home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / intel86 / help.p86 next >
Text File  |  2020-01-01  |  25KB  |  608 lines

  1. $large
  2.  
  3. help: do;
  4.  
  5. /*
  6.  *              HELP Utility Program
  7.  *  by Albert J. Goodman;  Edit date:  6-June-85
  8.  *
  9.  *  Gives help on a topic specified on the command line.  The
  10.  *  format of the command line is one or more keywords (separated
  11.  *  by one or more spaces and/or tabs), where each keyword after
  12.  *  the first is treated as a subtopic of topic specified by the
  13.  *  preceeding keyword(s).  Topic keywords may be abbreviated by any
  14.  *  amount; if an abbreviation is given which matches the beginning
  15.  *  of more than one topic in the help library file, the first matching
  16.  *  topic will be displayed.  If the first keyword begins with an
  17.  *  at-sign (@) the remainder of it is assumed to be the complete
  18.  *  pathname of the help library to be used for the source of the
  19.  *  help information.  If the command line does not begin with an
  20.  *  at-sign, the default help library will be used, which has the
  21.  *  same name (presumably HELP) and directory as this program with
  22.  *  the extension ".HLP".  The information required to create a
  23.  *  help library file is given below:
  24.  *
  25.  *  A help library is conceptually a tree structure, with a root
  26.  *  help message and a list of subtopics, and similarly a help
  27.  *  message and a list of sub-subtopics for each of the subtopics,
  28.  *  and so on.  The structure of a help library file is defined by
  29.  *  control lines beginning with a delimiter character (which may
  30.  *  nevertheless be used freely within help text if not at the
  31.  *  beginning of a line).  Each help file has its own delimiter
  32.  *  character (which may be any character desired, but should not
  33.  *  be a digit because it's used to delimit numbers), defined by the
  34.  *  first character of the file.  The remainder of the first line
  35.  *  of the file is ignored (thus it may be used for identification
  36.  *  of author, date, or other comments).  Normal control lines each
  37.  *  begin with the delmiter, followed by a (decimal) number indicating
  38.  *  the nesting level of the help which follows this control line,
  39.  *  followed by the delimiter again to mark the end of the number.
  40.  *  A nesting level of one means a subtopic of the root; in other words,
  41.  *  the nesting level is the position of the associated keyword in a
  42.  *  command line (which may range from 1 to MAX$KEYWORDS minus one).
  43.  *  The rest of the control line contains the (sub)topic keyword for that
  44.  *  level which identifies the help text which follows this control line.
  45.  *  Thus the file begins with the delimiter on a line the rest of which
  46.  *  is ignored; following that comes zero or more lines of root help
  47.  *  text, terminated by the next line beginning with the delimiter; this
  48.  *  should contain the first subtopic keyword and a nesting level of
  49.  *  one.  All sub-subtopic control lines are taken to be subtopics of
  50.  *  the most recent previous control line with a nesting level one
  51.  *  lower than theirs.  Finally, the last help text in the file (for
  52.  *  the deepest nested help under the last keyword at each level above
  53.  *  it) must be terminated by a special control line consisting of the
  54.  *  delimiter followed by the word END (in upper or lower case) followed
  55.  *  by a final occurrence of the delimter.  This marks the end of the help
  56.  *  library file as far as the HELP program is concerned:  anything in
  57.  *  the file after this control line will be ignored, and if the physical
  58.  *  end-of-file is encountered before this control line an error message
  59.  *  will be generated.  Also, the HELP program will indent all help text
  60.  *  by an amount determined by its nesting level, so there is no need for
  61.  *  indentation in the help library file.  Similarly, blank lines between
  62.  *  the control lines and the help text are supplied and thus need not be
  63.  *  in the file.
  64.  */
  65.  
  66.  
  67. /* Get all iRMX 86 system call external declarations */
  68. $include(:I:RMX.EXT)
  69.  
  70. declare
  71.         MAX$KEYWORDS    literally   '9',    /* Maximum topic keywords + 1 */
  72.         MAX$KEYWORD$LEN literally   '23',   /* Maximum length of a keyword */
  73.         boolean         literally   'byte', /* Another useful type */
  74.         TRUE            literally   '0FFh', /* Boolean constant */
  75.         FALSE           literally   '000h', /* ditto */
  76.         CR              literally   '0Dh',  /* ASCII Carriage-return */
  77.         LF              literally   '0Ah',  /* ASCII Line-feed character */
  78.         HT              literally   '09h',  /* ASCII tab character */
  79.         status          word,   /* Used for every system call */
  80.         file$token      token,  /* Connection to the help library file */
  81.         delim           byte,   /* Special delimiter character in help file */
  82.         level           byte,   /* Current nesting level being scanned for */
  83.         char            byte,   /* Current character being scanned */
  84.         ( i, j )        byte,   /* General-purpose array index or counters */
  85.         finished        boolean,    /* Whether finished giving help */
  86.         file$name       structure(  /* Buffer for help library file name */
  87.                             len         byte,
  88.                             ch( 50 )    byte),
  89.         num$keywords    byte,   /* Number of keywords in KEYWORD buffer */
  90.         keyword( MAX$KEYWORDS ) structure(  /* Buffer for topic keywords */
  91.                             len                     byte,
  92.                             ch( MAX$KEYWORD$LEN )   byte),
  93.         line$buffer     structure(  /* General-purpose line buffer */
  94.                             len         byte,
  95.                             ch( 80 )    byte);
  96.  
  97.  
  98. /*
  99.  *
  100.  *      System-dependent utility procedures.
  101.  *
  102.  */
  103.  
  104.  
  105. print: procedure( string$ptr );
  106.  
  107.     /*
  108.      *  Print a string (length byte followed by that many
  109.      *  characters) on the console.
  110.      */
  111.  
  112.     declare
  113.         string$ptr  pointer;
  114.  
  115.     call rq$c$send$co$response( 0, 0, string$ptr, @status );
  116.  
  117. end print;
  118.  
  119.  
  120. new$line: procedure;
  121.  
  122.     /*
  123.      *  Get the cursor to a new line (i.e. print CR/LF).
  124.      */
  125.  
  126.     call print( @( 2,CR,LF ) );
  127.  
  128. end new$line;
  129.  
  130.  
  131. print$char: procedure( char );
  132.  
  133.     /*
  134.      *  Print a single character (since PRINT only prints a string).
  135.      */
  136.  
  137.     declare
  138.         char    byte,
  139.         string  structure(
  140.                     len     byte,
  141.                     ch      byte);
  142.  
  143.     string.len = 1;     /* Form a one-character string */
  144.     string.ch = char;
  145.     call print( @string );  /* and print it */
  146.  
  147. end print$char;
  148.  
  149.  
  150. abort$program: procedure( error$msg$ptr, file$name$ptr );
  151.  
  152.     /*
  153.      *  Abort the program, displaying the error message pointed to
  154.      *  by ERROR$MSG$PTR, followed by the string pointed to by
  155.      *  FILE$NAME$PTR in quotes, followed by " -- HELP aborted."
  156.      *  If FILE$NAME$PTR is zero then it is skipped (including the
  157.      *  quotes), and if ERROR$MSG$PTR is zero no message is displayed.
  158.      */
  159.  
  160.     declare
  161.         ( error$msg$ptr, file$name$ptr )    pointer;
  162.  
  163.     if ( error$msg$ptr <> 0 ) then  /* If we have an error message */
  164.       do;
  165.         call print( error$msg$ptr );    /* Print error message */
  166.         if ( file$name$ptr <> 0 ) then  /* we have a filename also */
  167.           do;
  168.             call print( @( 2,' "' ) );  /* open quote */
  169.             call print( file$name$ptr );    /* the filename */
  170.             call print$char( '"' );     /* close quote */
  171.           end;
  172.         call print( @( 17,' -- HELP aborted.' ) );
  173.       end;  /* if ( error$msg$ptr <> 0 ) */
  174.     call new$line;  /* Get to a new line to tidy up display */
  175.     call rq$exit$io$job( 0, 0, @status );   /* And exit the program */
  176.  
  177. end abort$program;
  178.  
  179.  
  180. check$status: procedure;
  181.  
  182.     /*
  183.      *  Check the exception code returned by a system call to the global
  184.      *  variable STATUS.  If it is not E$OK, display the exception code
  185.      *  and mnemonic at the console and abort the program.
  186.      */
  187.  
  188.     if ( status <> E$OK ) then
  189.       do;   /* Handle an exceptional condition */
  190.         /* Get the exception code and mnemonic into the line buffer */
  191.         line$buffer.len = 0;    /* Init to null string */
  192.         call rq$c$format$exception( @line$buffer, size( line$buffer ),
  193.                                         status, 1, @status );
  194.         /* Display the error message and abort the program */
  195.         call abort$program( @line$buffer, 0 );
  196.       end;  /* if ( status <> E$OK ) */
  197.  
  198. end check$status;
  199.  
  200.  
  201. disable$exception$handler: procedure;
  202.  
  203.     /*
  204.      *  Disable the default exception handler, to prevent it from gaining
  205.      *  control and aborting the program as soon as any exception occurs.
  206.      */
  207.  
  208.     declare
  209.         exception$handler$info  structure(
  210.                                     offset  word,
  211.                                     base    word,
  212.                                     mode    byte);
  213.  
  214.     exception$handler$info.offset = 0;
  215.     exception$handler$info.base = 0;
  216.     exception$handler$info.mode = 0;    /* Never pass control to EH */
  217.     call rq$set$exception$handler( @exception$handler$info, @status );
  218.     call check$status;
  219.  
  220. end disable$exception$handler;
  221.  
  222.  
  223. open$file: procedure( name$ptr ) boolean;
  224.  
  225.     /*
  226.      *  Open the file specified in the string (length byte followed
  227.      *  by the characters of the name) pointed to by NAME$PTR, which is
  228.      *  assumed to already exist, for reading.  Sets the global FILE$TOKEN.
  229.      *  Returns TRUE if the open was successful, otherwise it prints
  230.      *  an error message on the console describing the problem
  231.      *  encountered and returns FALSE.
  232.      */
  233.  
  234.     declare
  235.         name$ptr    pointer;
  236.  
  237.     /* Try to open the file */
  238.     file$token = rq$c$get$input$connection( name$ptr, @status );
  239.     if ( status = E$OK ) then   /* we were successful */
  240.         return( TRUE );
  241.     else    /* the operation failed */
  242.         return( FALSE );    /* an error message has already been displayed */
  243.  
  244. end open$file;
  245.  
  246.  
  247. read$char: procedure byte;
  248.  
  249.     /*
  250.      *  Return the next character from the file specified by the global
  251.      *  token FILE$TOKEN (which must be open for reading).
  252.      *  If end-of-file is encountered, it aborts the program with an
  253.      *  error message.
  254.      */
  255.  
  256.     declare
  257.         bytes$read              word,
  258.         ch                      byte;
  259.  
  260.     /* Read the next byte from the file */
  261.     bytes$read = rq$s$read$move( file$token, @ch, 1, @status );
  262.     call check$status;
  263.     if ( bytes$read = 0 ) then  /* we ran into end-of-file */
  264.         call abort$program( @( 25,'Unexpected end-of-file in' ), @file$name );
  265.     else    /* we got a character */
  266.         return( ch );       /* so return it */
  267.  
  268. end read$char;
  269.  
  270.  
  271. upcase: procedure( x ) byte;
  272.  
  273.     /*
  274.      *  Force an ASCII letter to upper-case;
  275.      *  a non-letter is returned unchanged.
  276.      */
  277.  
  278.     declare
  279.         x   byte;
  280.  
  281.     if ( ( x >= 'a' ) and ( x <= 'z' ) ) then   /* it was lower-case */
  282.         return( x - 'a' + 'A' );    /* return the upper-case equivalent */
  283.     else    /* it was anything else */
  284.         return( x );    /* just return it unchanged */
  285.  
  286. end upcase;
  287.  
  288.  
  289. read$number: procedure byte;
  290.  
  291.     /*
  292.      *  Read a number from the file, terminated by the delimiter.
  293.      *  If the characters up to the next delimiter do not form an
  294.      *  integer (i.e. contain a non-digit--other than the word END--
  295.      *  or contain no characters at all), abort with an appropriate
  296.      *  error message; otherwise, return the value of the number.
  297.      *  The file pointer is left after the terminating delimiter.
  298.      *  If the "number" consists of the word END, zero is returned.
  299.      *  (Otherwise the number is in base 10.)  If the number has
  300.      *  more than 8 characters it will be truncated.
  301.      */
  302.  
  303.     declare
  304.         num     byte,
  305.         i       byte,
  306.         string  structure(
  307.                     len     byte,
  308.                     ch( 8 ) byte);
  309.  
  310.     string.len = 0;
  311.     string.ch( string.len ) = read$char;    /* Read first char of number */
  312.     do while ( string.ch( string.len ) <> delim );  /* Read rest of number */
  313.         if ( string.len < last( string.ch ) ) then  /* room for more digits */
  314.             string.len = ( string.len + 1 );    /* move to next digit */
  315.         string.ch( string.len ) = read$char;    /* Read next character */
  316.     end;    /* do while ( string.ch( string.len ) <> delim ) */
  317.     num = 0;    /* Init number to zero */
  318.     if ( string.len = 0 ) then  /* we got nothing at all */
  319.         call abort$program( @( 17,'Missing number in' ), @file$name );
  320.     else if ( ( string.len <> 3 ) or
  321.                 ( upcase( string.ch( 0 ) ) <> 'E' ) or
  322.                 ( upcase( string.ch( 1 ) ) <> 'N' ) or
  323.                 ( upcase( string.ch( 2 ) ) <> 'D' ) ) then  /* it's not END */
  324.         do i = 0 to ( string.len - 1 );     /* for each digit */
  325.             if ( ( string.ch( i ) < '0' ) or ( string.ch( i ) > '9' ) ) then
  326.               do;   /* Handle error of non-digit */
  327.                 call print( @( 16,'Invalid number "' ) );
  328.                 call print( @string );  /* show what we got */
  329.                 call abort$program( @( 4,'" in' ), @file$name );
  330.               end;  /* if ... -- it's not a digit */
  331.             /* Combine this digit into the number */
  332.             num = ( ( num * 10 ) + ( string.ch( i ) - '0' ) );
  333.         end;    /* do i = 0 to ( string.len - 1 ) */
  334.     return( num );      /* Return the number we got (zero if it was END) */
  335.  
  336. end read$number;
  337.  
  338.  
  339. read$line: procedure;
  340.  
  341.     /*
  342.      *  Read the current line from the file into the global LINE$BUFFER
  343.      *  up to the next LF (line-feed) character.
  344.      */
  345.  
  346.     declare
  347.         ch      byte;
  348.  
  349.     line$buffer.len = 0;
  350.     line$buffer.ch( line$buffer.len ) = read$char;  /* Read first char */
  351.     do while ( line$buffer.ch( line$buffer.len ) <> LF );
  352.         if ( line$buffer.len < last( line$buffer.ch ) ) then
  353.             line$buffer.len = ( line$buffer.len + 1 );  /* Bump len if room */
  354.         line$buffer.ch( line$buffer.len ) = read$char;  /* Read next char */
  355.     end;    /* do while ( line$buffer.ch( line$buffer.len ) <> LF ) */
  356.     line$buffer.len = ( line$buffer.len + 1 );  /* Count final char (LF) */
  357.  
  358. end read$line;
  359.  
  360.  
  361. skip$text: procedure;
  362.  
  363.     /*
  364.      *  Skip a single help text entry.  That is, read and discard lines
  365.      *  from the file until reaching a line which begins with DELIM.
  366.      *  The file pointer will be left just after this character, i.e.
  367.      *  the second character of the control line.  If the first character
  368.      *  read at the current position is DELIM, only that character will
  369.      *  be read (i.e. it is assumed that we are at the beginning of a
  370.      *  line now).
  371.      */
  372.  
  373.     declare
  374.         ch      byte;
  375.  
  376.     ch = read$char;     /* Get first character of this line */
  377.     do while ( ch <> delim );   /* As long as it's not a control line */
  378.         call read$line;     /* Skip that line */
  379.         ch = read$char;     /* And check on the next one */
  380.     end;    /* do while ( ch <> delim ) */
  381.  
  382. end skip$text;
  383.  
  384.  
  385. keyword$match: procedure( knum ) boolean;
  386.  
  387.     /*
  388.      *  Compare KEYWORD( KNUM ) with the contents of LINE$BUFFER.
  389.      *  Return TRUE if they match (the keyword may be an abbreviation
  390.      *  of LINE$BUFFER), FALSE otherwise.
  391.      */
  392.  
  393.     declare
  394.         ( knum, i ) byte;
  395.  
  396.     i = 0;
  397.     do while ( ( i < keyword( knum ).len ) and
  398.                 ( i < line$buffer.len ) and
  399.                 ( line$buffer.ch( i ) <> CR ) );
  400.         if keyword( knum ).ch( i ) <> upcase( line$buffer.ch( i ) ) then
  401.             return( FALSE );    /* Don't match */
  402.         i = ( i + 1 );  /* check next character */
  403.     end;    /* do while ... */
  404.     if ( i < keyword( knum ).len ) then     /* keyword too long */
  405.         return( FALSE );
  406.     else    /* It matches */
  407.         return( TRUE );
  408.  
  409. end keyword$match;
  410.  
  411.  
  412. print$spaces: procedure( num );
  413.  
  414.     /*
  415.      *  Print NUM spaces (i.e. indent by that many characters).
  416.      *  NUM must be no more than 20 unless the length of SPACES
  417.      *  (below) is increased.
  418.      */
  419.  
  420.     declare
  421.         num         byte,
  422.         spaces(*)   byte data( 20,'                    ' ),
  423.         count       byte at ( @spaces );
  424.  
  425.     count = num;    /* Set the length to be printed this time */
  426.     call print( @spaces );  /* Print COUNT spaces */
  427.  
  428. end print$spaces;
  429.  
  430.  
  431. show$line: procedure( level, char, line$ptr );
  432.  
  433.     /*
  434.      *  Display the string pointed to by LINE$PTR, preceeded by the
  435.      *  character CHAR, indented appropriately for LEVEL of nesting.
  436.      */
  437.  
  438.     declare
  439.         ( level, char ) byte,
  440.         line$ptr        pointer;
  441.  
  442.     call print$spaces( 2 * level ); /* Indent two spaces per level */
  443.     if ( char <> 0 ) then   /* if we got a leading charcter */
  444.         call print$char( char );    /* Display it */
  445.     call print( line$ptr ); /* And print the line */
  446.  
  447. end show$line;
  448.  
  449.  
  450. /*
  451.  *
  452.  *      Main program -- HELP
  453.  *
  454.  */
  455.  
  456.  
  457. call new$line;  /* Leave a blank line */
  458. call disable$exception$handler;
  459.  
  460. /* Parse the command line */
  461. char = ' ';     /* Insure at least one pass through the WHILE loop: */
  462. do while ( char = ' ' );    /* Until we get the first non-space */
  463.     char = rq$c$get$char( @status );    /* Get next char from command line */
  464.     call check$status;
  465. end;    /* do while ( char = ' ' ) */
  466. if ( char = '@' ) then      /* We have a help library filespec */
  467.   do;   /* Get the filespec into the filename buffer */
  468.     call rq$c$get$input$path$name( @file$name, size( file$name ), @status );
  469.     call check$status;
  470.     if ( file$name.len = 0 ) then   /* no pathname there */
  471.         call abort$program( @( 34,'No help library pathname follows @' ), 0 );
  472.     char = rq$c$get$char( @status );    /* And get next character */
  473.     call check$status;
  474.   end;  /* if ( char = '@' ) */
  475. else    /* No at-sign, so use default help library */
  476.   do;   /* Get its name into the filename buffer */
  477.     /* Get the name of the file containing this program */
  478.     call rq$c$get$command$name( @file$name, size( file$name ), @status );
  479.     call check$status;
  480.     /* Append the .HLP suffix to it, forming the name of the help library */
  481.     call movb( @( '.HLP' ), @file$name.ch( filename.len ), 4 );
  482.     file$name.len = ( file$name.len + 4 );
  483.   end;  /* else -- no at-sign */
  484. if ( open$file( @file$name ) ) then   /* Open the help library file */
  485.   do;   /* Successfully opened, so parse rest of command line and give help */
  486.     i = 0;  /* Start with the first keyword */
  487.     keyword( i ).len = 0;   /* Init first keyword to null */
  488.     do while ( ( char <> 0 ) and ( char <> CR ) );  /* until end of line */
  489.         if ( ( char = HT ) or ( char = ' ' ) ) then /* it's a space or tab */
  490.           do;
  491.             if ( keyword( i ).len > 0 ) then    /* end of this keyword */
  492.               do;
  493.                 if ( i < last( keyword ) ) then
  494.                     i = ( i + 1 );      /* Move to next keyword */
  495.                 keyword( i ).len = 0;   /* and init it to null */
  496.               end;  /* if ( keyword( i ).len > 0 ) */
  497.             /* else ignore redundant space or tab */
  498.           end;  /* if ( ( char = HT ) or ( char = ' ' ) ) */
  499.         else    /* non-space and non-tab character */
  500.           do;
  501.             if ( keyword( i ).len < size( keyword.ch ) ) then
  502.               do;
  503.                 /* Store character of keyword, capitalized */
  504.                 keyword( i ).ch( keyword( i ).len ) = upcase( char );
  505.                 keyword( i ).len = ( keyword( i ).len + 1 );
  506.               end;  /* if ( keyword( i ).len < size( keyword.ch ) ) */
  507.           end;  /* else -- non-space and non-tab character */
  508.         char = rq$c$get$char( @status );    /* Get the next character */
  509.     end;    /* do while ( ( char <> 0 ) and ( char <> CR ) ) */
  510.     if ( ( keyword( i ).len > 0 ) and ( i < last( keyword ) ) ) then
  511.         i = ( i + 1 );      /* Count final keyword */
  512.     num$keywords = i;   /* Save number of keywords we got */
  513.  
  514.     /* Begin reading help library file */
  515.     char = read$char;   /* Get first character of file (special delimiter) */
  516.     delim = char;   /* Save special delimiter for this file */
  517.     call read$line; /* Discard the rest of the first line */
  518.     level = 1;      /* Init level number we're looking for */
  519.     finished = FALSE;   /* not finished yet */
  520.     do while ( not finished );      /* until we're finished giving help */
  521.         if ( num$keywords >= level ) then   /* got a keyword for this level */
  522.           do;
  523.             call skip$text;     /* Skip previous entry */
  524.             i = read$number;    /* Get nesting level for next entry */
  525.             call read$line;     /* And read its keyword */
  526.             if ( i < level ) then   /* found an entry at a lower level */
  527.               do;
  528.                 call show$line( level, 0,
  529.                             @( 28,'Sorry, no help available on' ) );
  530.                 do i = 0 to ( level - 1 );
  531.                     call print$char( ' ' );
  532.                     call print( @keyword( i ) );
  533.                 end;    /* do i = 0 to ( level - 1 ) */
  534.                 call new$line;
  535.                 finished = TRUE;    /* No more help to give on this topic */
  536.               end;  /* if ( i < level ) */
  537.             else if ( i = level ) then  /* found entry for level we want */
  538.               do;
  539.                 if keyword$match( level - 1 ) then  /* keyword matches */
  540.                   do;   /* Show matching keyword */
  541.                     call show$line( level, 0, @line$buffer );
  542.                     call new$line;      /* And leave a blank line */
  543.                     level = ( level + 1 );  /* And go to next lower level */
  544.                   end;  /* if keyword$match( level - 1 ) */
  545.               end;  /* if ( i = level ) */
  546.           end;  /* if ( num$keywords >= level ) */
  547.         else if ( num$keywords = ( level - 1 ) ) then
  548.           do;   /* Display selected help text */
  549.             char = read$char;   /* Get first char */
  550.             do while ( char <> delim ); /* Until next control line */
  551.                 call read$line; /* Read the rest of this line of text */
  552.                 call show$line( level, char, @line$buffer );    /* show it */
  553.                 char = read$char;   /* Read first char of next line */
  554.             end;    /* do while ( char <> delim ) */
  555.             i = read$number;    /* Get level of next entry */
  556.             if ( i < level ) then   /* not a subtopic of selected entry */
  557.                 finished = TRUE;    /* no subtopics, so nothing more to do */
  558.             else    /* we have subtopic(s) to list */
  559.               do;
  560.                 call new$line;  /* Leave a blank line */
  561.                 call show$line( level, 0,
  562.                             @( 28,'Further help available on:',CR,LF ) );
  563.                 call new$line;  /* And leave another blank line */
  564.                 level = ( level + 1 );  /* Set level to list subtopics */
  565.                 call read$line; /* Read first subtopic keyword */
  566.                 line$buffer.len = ( line$buffer.len - 2 );  /* Remove CR/LF */
  567.                 j = line$buffer.len;    /* Save chars so far on this line */
  568.                 call show$line( level, 0, @line$buffer ); /* show keyword */
  569.               end;  /* else -- we have to list subtopics */
  570.           end;  /* if ( num$keywords = ( level - 1 ) ) */
  571.         else    /* we must be listing subtopics */
  572.           do;
  573.             call skip$text;     /* Skip previous entry */
  574.             i = read$number;    /* Get nesting level for next entry */
  575.             call read$line;     /* Read its keyword */
  576.             line$buffer.len = ( line$buffer.len - 2 );  /* And remove CR/LF */
  577.             if ( i < ( level - 1 ) ) then   /* found entry at a lower level */
  578.               do;   /* So no more subtopics of selected entry */
  579.                 call new$line;  /* Finish last line of list */
  580.                 finished = TRUE;    /* And we're all done */
  581.               end;  /* if ( i < ( level - 1 ) ) */
  582.             else if ( i = ( level - 1 ) ) then  /* found right level entry */
  583.               do;   /* Show another subtopic keyword */
  584.                 if ( j > 60 ) then  /* time to start a new line (60=4*15) */
  585.                   do;
  586.                     call new$line;
  587.                     call show$line( level, 0, @line$buffer );
  588.                     j = line$buffer.len;    /* Count chars on this line */
  589.                   end;  /* if ( j > 48 ) */
  590.                 else    /* Make another entry on this line */
  591.                   do;
  592.                     call print$spaces( 15 - ( j mod 15 ) ); /* align columns */
  593.                     j = ( j + ( 15 - ( j mod 15 ) ) + line$buffer.len );
  594.                     call print( @line$buffer );
  595.                   end;  /* else -- continue this line */
  596.               end;  /* if ( i = ( level - 1 ) ) */
  597.           end;  /* else -- listing subtopics */
  598.     end;    /* do while ( not finished ) */
  599.     /* Finished giving help on the selected topic */
  600.   end;  /* if ( open$file( @file$name ) ) */
  601. else    /* Error occurred when opening file, abort with message. */
  602.     call abort$program( @( 30,'Can''t access help library file' ),
  603.                                             @file$name );
  604.  
  605. call abort$program( 0, 0 );     /* Exit with no error message */
  606.  
  607. end help;
  608.