.CH "Ratfor Language Guide" .MH "What is Ratfor?" The Ratfor ("Rational Fortran") language was introduced in the book [ul Software Tools] .sb by Brian W. Kernighan and P. J. Plauger .xb (Addison-Wesley, 1976). There, the authors use it as the medium for the development of programs that may be used as cooperating tools. Ratfor offers many extensions to Fortran that encourage and facilitate structured design and programming, enhance program readability and ease the burden of coding. Through some very simple mechanisms, Ratfor helps the programmer to isolate machine and implementation dependent sections of his code. .pp Among the many programs developed in [ul Software Tools] is a Ratfor preprocessor -- a program for converting Ratfor into equivalent ANSI-66 Fortran. 'Rp', the preprocessor described in this guide, is an original version based on the program presented in [ul Software Tools.] .MH "Differences Between Ratfor and Fortran" As we mentioned, Ratfor and Fortran are very similar. Perhaps the best introduction to their differences is given by Kernighan and Plauger in [ul Software Tools:] .sp .lm +5 .rm -5 "But bare Fortran is a poor language indeed for programming .sb or describing programs. . . .xb . Ratfor provides modern control flow statements like those in PL/I, Cobol, Algol, or Pascal, so we can do structured programming properly. It is easy to read, write and understand, .sb and readily translates into Fortran. . . .xb . Except for a handful of new statements like [bf if - else, while,] and [bf repeat - until,] Ratfor [ul is] Fortran." .br .lm -5 .rm +5 .SH "Source Program Format" .PH "Case Sensitivity" In most cases, the format of Ratfor programs is much less restricted than that of Fortran programs. Since the Software Tools Subsystem encourages use of terminals with multi-case capabilities, 'rp' accepts input in both upper and lower case. 'Rp' is case sensitive. Keywords, such as [bf if] and [bf select,] must appear in lower case. Case is significant in identifiers; they may appear in either case, but upper case letters are not equivalent to lower case letters. For example, the words "blank" and "Blank" do .ul not represent the same identifier. For circumstances in which case sensitivity is a bother, 'rp' accepts a command line option ("-m") that instructs it to ignore the case of all identifiers and keywords. See the applications notes or the 'help' command for more details. .PH "Blank Sensitivity" Unlike most Fortran compilers, 'rp' is very sensitive to blanks. 'Rp' requires that all words be separated by at least one blank or special character. Words containing imbedded blanks are not allowed. The best rule of thumb is to remember that if it is incomprehensible to you, it is probably incomprehensible to 'rp.' (Remember, we humans normally leave blank spaces between words and tend not to place blanks inside words. Such things make text difficult to understand.) .pp As a bad example, the following Ratfor code is incorrect and will not be interpreted properly: .be .ne 5 subroutineexample(a,b,c) integera,b,c repeatx=x+1 until(x>1) .ee A few well placed blanks will have to be added before 'rp' can understand it: .be .ne 5 subroutine example(a,b,c) integer a,b,c repeat x=x+1 until(x>1) .ee You should note that extra spaces are allowed (and encouraged) everywhere except inside words and literals. Extra spaces make a program much more readable by humans: .be .ne 5 subroutine example (a, b, c) integer a, b, c repeat x = x + 1 until (x > 1) .ee .PH "Card Columns" As should be expected of any interactive software system, 'rp' is completely insensitive to "card" columns; statements may begin and end at any position in a line. Lines may be of any length, but identifiers and quoted strings may not be longer than 100 characters. 'Rp' will output all statements beginning in column 7, and automatically generate continuation lines for statements extending past column 72. All of the following are valid Ratfor statements, although such erratic indentation is definitely frowned upon. .be .ne 5 integer i, j i = 1 j = 2 stop end .ee .PH "Multiple Statements per Line" 'Rp' also allows multiple statements per line, although indiscriminate use of this feature is not encouraged. Just place a semicolon between statements and 'rp' will generate two Fortran statements from them. You will find .be .ne 3 integer i real a logical l .ee to be completely equivalent to .be .ne 1 integer i; real a; logical l .ee .PH "Statement Labels and Continuation" You may wonder what happens to statement labels and continuation lines, since 'rp' pays no attention to card columns. It turns out that statement labels and continuation lines are not often necessary. While 'rp' minimizes the need for statement labels (except on [bf format] statements) and is quite intelligent about continuation lines, there are conventions to take care of those situations where a label is required or the need for a continuation line is not obvious to 'rp.' .pp A statement may be labeled simply by placing the statement number, starting in any column, before the statement. Any executable statement, including the Ratfor control statements, may be labeled, and 'rp' will place the label correctly in the Fortran output. It is wise to refrain from using five-digit statement numbers; 'rp' uses these statement labels to implement the Ratfor control statements, and consequently will complain if it encounters them in a source program. As examples of statement labels, .be .ne 4 2 read (1, 10) a, b, c 10 format (3e10.0) write (1, 20) a, b, c; 20 format (3f20.5) go to 2 .ee all show statement numbers in use. You should note that with proper use of Ratfor and the Software Tools Subsystem support subroutines, statement labels are almost never required. .pp As for continuation lines, 'rp' is usually able to recognize when the current line needs to be continued. A line ending with a comma, unbalanced parentheses in a condition, or a missing statement (such as at the end of an [bf if)] are all situations in which 'rp' correctly anticipates a continuation line: .be .ne 8 integer a, b, c, d, e, f, g if (a == b & c == d & e == f & g == h & i == j & k == l) call eql if (a == b) c = -2 .ee .pp If an explicit continuation is required, such as in a long assignment statement, 'rp' can be made to continue a line by placing a trailing underscore ("_") at the end of the line. This underscore must be preceded by a space. You should note that the underscore is placed on the end of [ul line to be continued,] rather than on the [ul continuation line] as in Fortran. If you are unsure whether Ratfor will correctly anticipate a continuation line, go ahead and place an underscore on the line to be continued -- 'rp' will ignore redundant continuation indicators. .pp Identifiers may not be split between lines; continuation is allowed only between tokens. If you have an extremely long string constant that requires continuation, you can take advantage of the fact that 'rp' always concatenates two adjacent string constants. Just close the first part of the literal with a quote, space, and underscore, and begin the second part on the next line with a quote. 'Rp' will ignore the line break (because of the trailing underscore) and concatenate the two literals. .pp The following are some examples of explicit line continuations: .be .ne 9 i = i + j + k + l + m + n + o + p + q + r + _ s + t + u + v 1 format ("for inputs of ", i5, " and ", i5/ _ "the expected output should be ", i5) string heading _ "----------------------------------------------" _ "----------------------------------------------" .ee .PH "Comments" Comments, an important part of any program, can be entered on any line; a comment begins with a sharp sign ("#") and continues until the end of the line. In addition, blank lines and lines containing only comments may be freely placed in the source program. Here are some appropriate and (correct but) inappropriate uses of Ratfor comments: .be .ne 15 if (i > 48) # do this only if i is greater than 48 j = j + 1 data array / 1, # element 1 2, # element 2 3, # element 3 4/ # element 4 integer cnt, # counter for controlling the # outer loop total_errs, # total number of errors # encountered last_pass # flag for determining the # last pass; init = 0 .ee .SH "Identifiers" .pp A major difference between Ratfor and Fortran is Ratfor's acceptance of arbitrarily long identifiers. A Ratfor identifier may be up to 100 characters long, beginning with a letter, and may contain letters, digits, dollar signs, and underscores. However, it may [ul not] be a Ratfor or Fortran keyword, such as [bf if, else, integer, real,] or [bf logical.] Underscores are allowed in identifiers only for the sake of readability, and are always ignored. Thus, "these_tasks" and "the_set_asks" are equivalent Ratfor identifiers. .pp 'Rp' guarantees that an identifier longer than six characters will be transformed into a [ul unique Fortran identifier.] Normally, the process of transforming Ratfor identifiers into Fortran identifiers is transparent; you need not be concerned with how this transformation is accomplished. The one notable exception is the effect on external symbols (i.e. subroutine and function names, common block names). When the declaration of a subprogram and its invocation are preprocessed together, in the same run, no problems will occur. However, if the subprogram and its invocation are preprocessed separately, there is no guarantee that a given Ratfor name will be transformed into the same Fortran name in the two different runs. This situation can be avoided in either of three ways: (1) use the [bf linkage] statement described in the next section, (2) use six-character or shorter identifiers for subprogram names, or (3) preprocess subprograms and their invocations in the same run. .pp .ne 2 Just for pedagogical reasons, here are a few correct and incorrect Ratfor identifiers: .be .ne 16 [ul Correct] .sp long_name_1 long_name_2 prwf$$ I_am_a_very_long_Ratfor_name_that_is_perfectly_correct a_a # You should note that 'a_a', 'a__a', and 'aa' a__a # are all absolutely identical in Ratfor -- aa # underscores are always ignored in identifiers, AA # but 'AA' is very different. .sp 2 [ul Incorrect] .sp 123_part # starts with a digit _part1 # starts with an underscore part 2 # contains a blank a*b # contains an asterisk .ee .pp The following paragraph contains a description of exactly how Ratfor identifiers are transformed into Fortran identifiers. You need not know how this transformation is accomplished to make full use of Ratfor; hence, you probably need not read the next paragraph. .pp If a Ratfor identifier is longer than six characters or contains an upper case letter, it is made unique by the following procedure: .sp .in +5 .ti -5 .ta 6 .tc \ (1)\The identifier is padded with 'a's or truncated to five characters. Remaining characters are mapped to lower case. .ti -5 (2)\The first character is retained to preserve implicit typing. .ti -5 (3)\The sixth character is changed to a "uniquing character" (normally a zero). .ti -5 (4)\If necessary, the second, third, fourth, and fifth characters are altered to make sure there is no conflict with a previously used identifier. .sp .in -5 'Rp' also examines six-character identifiers containing the uniquing character in the sixth position, to ensure that no conflicts arise. .SH "Integer Constants" Since it is sometimes necessary to use other than decimal integer constants in a program, 'rp' accepts integers in bases 2 through 16. Integers consisting of only digits are, of course, considered decimal integers. Other bases can be indicated with the following notation: .be r .ee where is the base of the number (in decimal) and is number in the desired base (the letters 'a' through 'f' are used to represent the digits '10' through '15' in bases greater than 10). For example, here are some Ratfor integer constants and the decimal values they represent: .ne 7 .be .ta 12 .ul Number\Decimal Value 8r77\63 16rff\255 -2r11\-3 7r13\10 .ee .pp Some care must be exercised when using this form of constant to generate bit-masks with the high-order bit set. For example, to set the high-order bit in a 16-bit word, one might be tempted to use one of the constants .be 16r8000 or 8r100000 .ee Either of these would cause incorrect results, because the value that they represent, in decimal, is 65536. This number, when encountered by Prime Fortran, is converted to a 32-bit constant (with the high order bit in the second word set). This is probably not the desired result. The only solutions to this problem (which occurs when trying to represent a negative twos-complement number as a positive number) are (1) use the correct twos-complement representation (-32768 in this case), or (2) fall back to Prime Fortran's octal constants (e.g. :100000). .SH "String Constants" Under the Software Tools Subsystem, character strings come in various flavors. Because various internal representations are used for character strings, Fortran Hollerith constants are not sufficient to easily provide all the different formats required. .pp All types of Ratfor string constants consist of a string body followed by a string format indicator. The body of a string constant consists of strings of characters bounded by pairs of quotes (either single or double quotes), possibly separated by blanks. All the character strings in the body (not including the bounding quotes) are concatenated to give the value of the string constant. For example, here are three string constant bodies that contain the same string: .ne 5 .be "I am a string constant body" "I" ' am ' "a" ' string ' "constant" ' body' "I am a string "'constant body' .ee .pp The string format indicator is an optional letter that determines the internal format to be used when storing the string. Currently there are five different string representations available: .sp .in +9 .ta 10 .ti -9 omitted\Fortran Hollerith string. When the string format indicator is omitted, a standard Fortran Hollerith constant is generated. Characters are left-justified, packed in words (two characters per word on the Prime), and unused positions on the right are filled with blanks. .sp .ti -9 c\Single character constant. The 'c' string format indicator causes a single character constant to be generated. The character is right-justified and zero-filled on the left in a word. Only one character is allowed in the body of the constant. Since it is easy to manipulate and compare characters in this format, it is the preferred format for all single characters in the Software Tools Subsystem. .sp .ti -9 p\Packed (Hollerith) period-terminated string. The 'p' format indicator causes the generation of a Fortran Hollerith constant containing the characters in the string body followed by a period. In addition, all periods in the string body are preceded by an escape character ("@@"). The advantage of a "p" format string over a Fortran Hollerith string is that the length of the "p" format string can be determined at run time. .sp .ti -9 v\PL/I character varying string. For compatibility with Prime's PL/I and because this data format is required by some system calls, the "v" format indicator will generate Fortran declarations to create a PL/I character varying string. The first word of the constant contains the number of characters; subsequent words contain the characters of the string body packed two per word. "V" format string constants may only be used in executable statements. .sp .ti -9 s\EOS-terminated unpacked string. The "s" string format indicator causes 'rp' to generated declarations necessary to construct an array of characters containing each character in the string body in a separate word, right-justified and zero-filled (each character is in the same format as is generated by the "c" format indicator). Following the characters is a word containing a value different from any character value that marks the end of the string. This ending value is defined as the symbolic constant EOS. EOS-terminated strings are the preferred format for multi-character strings in the Subsystem, and are used by most Subsystem routines dealing with character strings. "S" format string constants may only be used in executable statements. .in -9 .pp Here are some examples of strings and the result that would be generated for Prime Fortran. On a machine with a different character set or word length, different code might be generated. .ne 2 .be 8 .ta 21 .in +20 .ti -20 .ul String Constant\Resulting Code .fi .ti -20 'v'c\the integer constant 246 .ti -20 "=doc="s\an integer array of length 6 containing 189, 228, 239, 227, 189, 0 .ti -20 "a>b c>d"v\an integer array containing 7, "a>", "b ", "c>", "d " .ti -20 ".main."p\the constant 9h@@.main@@.. .ti -20 "Hollerith"\the constant 9hHollerith .in -20 .ee .SH "Logical and Relational Operators" Ratfor allows the use of graphic characters to represent logical and relational operators instead of the Fortran ".EQ." and such. While use of these graphic characters is encouraged, it is not incorrect to use the Fortran operators. The following table shows the equivalent syntaxes: .sp 2 .in +10 .nf .ta 10 20 .ne 12 .ul Ratfor\Fortran\Function .sp >\.GT.\Greater than >=\.GE.\Greater or equal <\.LT.\Less than <=\.LE.\Less or equal ==\.EQ.\Equal to ~=\.NE.\Not equal to .sp ~\.NOT.\Logical negation &\.AND.\Logical conjunction |\.OR.\Logical disjunction .fi .in -10 .sp 2 Note than the digraphs shown in the table must appear in the Ratfor program with no imbedded spaces. .pp For example, the two following [bf if] statements are equivalent in every way: .ne 5 .be if (a .eq. b .or. .not. (c .ne. d .and. f .ge. g)) if (a == b | ~ (c ~= d & f >= g)) .ee .pp In addition to graphics representing Fortran operators, two additional operators are available in any logical expression parsed by 'rp' (i.e. anywhere but assignment statements). These operators, '&&' ("and if") and '||' ("or if") perform the same action as the logical operators '&' and '|', except that they guarantee that the expression is evaluated from left to right, and that evaluation is terminated when the truth value of the expression is known. They may appear within the scope of the '~' operator, but they may not grouped within the scope of '&' and '|'. .pp These operators find use in situations in which it may be illegal or undesirable to evaluate the right-hand side of a logical expression based on the truth value of the left-hand side. For example, in .ne 4 .be while (i > 0 && str (i) == ' 'c) i = i - 1 .ee it is necessary that the subscript be checked before it is used. The order of evaluation of Fortran logical expressions is not specified, so in this example, it would be technically illegal to use '&' in place of '&&'. If the value of 'i' were less than 1, the illegal subscript reference might be made regardless of the range check of the subscript. The Ratfor short-circuited logical operators prevent this problem by insuring that "i > 0" is evaluated first, and if it is false, evaluation of the expression terminates, since its value (false) is known. .SH "Assignment Operators" Ratfor provides shorthand forms for the Fortran idioms of the form .be = .ee In Ratfor, this assignment can be simplified to the form .be .ee with the use of assignment operators. The following assignment operators are available: .ne 12 .be .ta 10 26 .ti -3 .ul Operator\ Use\ Result +=\ += \ = + () -=\ -= \ = - () *=\ *= \ = * () /=\ /= \ = / () %=\ %= \ = mod (, ) &=\ &= \ = and (, ) |=\ |= \ = or (, ) ^=\ ^= \ = xor (, ) .ee The Ratfor assignment operators may be used wherever a Fortran assignment statement is allowable. Regrettably, the assignment operators provide only a shorthand for the programmer; they do not affect the efficiency of the object code. .pp The assignment operators are especially useful with subscripted variables; since a complex subscript expression need appear only once, there is no possibility of mistyping or forgetting to change one. Here are some examples of the use of assignment operators .ne 7 .be i += 1 fact *= i + 10 subs (2 * i - 2, 5 * j - 23) -= 1 int %= 10 ** j mask &= 8r12 .ee For comparison, here are the same assignments without the use of assignment operators: .ne 7 .be i = i + 1 fact = fact * (i + 10) subs (2*i-2, 5*j-23) = subs (2*i-2, 5*j-23) - 1 int = mod (int, (10 ** j)) mask = and (mask, 8r12) .ee .SH "Fortran Statements in Ratfor Programs" Ratfor provides the escape statement to allow Fortran statements to be passed directly to the output without the usual processing, such as case mapping and automatic continuation. The escape statement has three forms, summarized below. In the first form listed below, the first non-blank character of the Fortran statement is output in column seven. In the second form, the first non-blank character of the Fortran statement is output in column seven, but column six contains a "$" to continue a previous Fortran statement to that stream. In the third form, the Fortran statement is output starting in column one, so that the user has full control of the placement of items on the line. The following is a summary of this description: .be .ul Escape Statement Format Output Column .sp % 7 %& 6 %% 1 .ee "Stream" can take on the following values: .be 1 declaration 2 data 3 code .ee If no stream is specified (i.e. %%), the Fortran statement is sent to the code stream. [cc]mc | .pp Escaped statements [ul must] occur inside a program unit, i.e., between a [bf function] or [bf subroutine] statement, and its corresponding [bf end] statement. Otherwise 'rp' gets confused about where the escaped statements should go, since it won't have any streams open. If you have a large amount of self contained FORTRAN that you want 'rp' to include in its output, you can accomplish this in two steps. First, put '%1%' at the beginning of each line, and then put the FORTRAN at the [ul beginning] of your ratfor source file. [cc]mc .SH "Incompatibilities" Even with the great similarities between Fortran and Ratfor, an arbitrary Fortran program is [ul not] necessarily a correct Ratfor program. Several areas of incompatibilities exist: .in +5 .rm -5 .ta 6 .tc \ .sp .ti -5 -\In Ratfor, blanks are significant -- at least one space must separate adjacent identifiers. .sp .ti -5 -\The Ratfor [bf do] statement, as we shall soon see, does not contain the statement number following the "do". Instead, its range extends over the next (possibly compound) statement. .sp .ti -5 -\Two word Fortran key phrases such as [bf double precision, block data,] and [bf stack header] must be presented as a single Ratfor identifier (e.g. "blockdata" or "block_data"). .sp .ti -5 -\Fortran statement functions must be preceded by the Ratfor keyword [bf stmtfunc.] To assure that they will appear in the correct order in the Fortran, they should immediately precede the [bf end] statement for the program unit. .sp .ti -5 -\Hollerith literals (i.e. 5HABCDE) are not allowed anywhere in a Ratfor program. Instead, 'rp' expects all Hollerith literals to be enclosed in single or double quotes (i.e. "ABCDE" or 'ABCDE'). 'Rp' will convert the quoted string into a proper Fortran Hollerith string. .sp .ti -5 -\'Rp' does not allow Fortran comments. In Ratfor, comments are introduced by a sharp sign ("#") appearing anywhere on a line, and continue to the end of the line. .sp .ti -5 -\'Rp' does not accept the Fortran continuation convention. Continuation is implicit for any line ending with a comma, or any conditional statement containing unbalanced parentheses. Continuation between arbitrary words may be indicated by placing an underscore, preceded by at least one space, at the end of the line to be continued. .sp .ti -5 -\'Rp' does not ignore text beyond column 72. .sp .ti -5 -\Fortran and Ratfor keywords may not be used as identifiers in a Ratfor program. Their use will result in unreasonable behavior. .in -5 .rm +5 .bp .MH "Ratfor Text Substitution Statements" .pp 'Rp' provides several text substitution facilities to improve the readability and maintainability of Ratfor programs. You can use these facilities to great advantage to hide tedious implementation details and to assist in writing transportable code. .SH "Define" .pp The Ratfor [bf define] statement bears a vague similarity to the non-standard Fortran [bf parameter] declaration, but is much more flexible. In Ratfor, any legal identifier may be defined as almost any string of characters. Thereafter, 'rp' will replace all occurrences of the defined identifier with the definition string. In addition, identifiers may be defined with a formal parameter list. Then, during replacement, actual parameters specified in the invocation are substituted for occurrences of the formal parameters in the replacement text. .pp Defines find their principle use in helping to clarify the meaning of "magic numbers" that appear frequently. For example, .be .ne 2 while (getlin (line, -10) ~= -1) call putlin (line, -11) .ee is syntactically correct, and even does something useful. But what? The use of [bf define] to hide the magic numbers not only allows them to be changed easily and uniformly, but also gives the program reader a helpful hint as to what is going on. If we rewrite the example, replacing the numbers by defined identifiers, not only are the numbers easier to change uniformly at some later date, but also, the reader is given a little bit of a hint as to what is intended. .be .ne 6 define (EOF, -1) define (STANDARD_INPUT, -10) define (STANDARD_OUTPUT, -11) .sp while (getlin (line, STANDARD_INPUT) ~= EOF) call putlin (line, STANDARD_OUTPUT) .ee .pp The last example also shows the syntax for definitions without formal parameters. .pp Often there are situations in which the replacement text must vary slightly from place to place. For example, let's take the last situation in which the programmer must supply "STANDARD_INPUT" and "STANDARD_OUTPUT" in calls to the line input and output routines. Since this occurs in a large majority of cases, it would be more convenient to have procedures named, say "getl" and "putl" that take only one parameter and assume "STANDARD_INPUT" or "STANDARD_OUTPUT". We could, of course, write two new procedures to fill this need, but that would add more code and more procedure calls. Two [bf define] statements will serve the purpose very well: .ne 9 .be define (STANDARD_INPUT, -10) define (STANDARD_OUTPUT, -11) define (getl (ln), getlin (ln, STANDARD_INPUT)) define (putl (ln), putlin (ln, STANDARD_OUTPUT)) .sp while (getl (line) ~= EOF) call putl (line) .ee In this case, when the string "getl (line)" is replaced, all occurrences of "ln" (the formal parameter) will be replaced by "line" (the actual parameter). This example will give exactly the same results as the first, but with a little less typing when "getl" and "putl" are called often. .pp The full syntax for a [bf define] statement follows: .be .in -2 define ( [()], ) .in +2 .ee When such a [bf define] statement is encountered, is recorded as the value of . At any later time, if is encountered in the text, it is replaced by the text of . If the original [bf define] contained a formal parameter list, the list of actual parameters following is collected, and the actual parameters are substituted for the corresponding formal parameters in before the replacement is made. .pp There is a file of "standard" definitions used by all Subsystem programs called "=incl=/swt_def.r.i". The [bf define] statements in this file are automatically inserted before each source file (unless 'rp' is told otherwise by the "-f" command line option). For information on the exact contents of this file, see Appendix D. .pp There are also a few other facts that are helpful when using [bf define:] .in +5 .sp .ti -5 -\The may be any string of characters not containing unbalanced parentheses or unpaired quotes .sp .ti -5 -\ must be identifiers. .sp .ti -5 -\ may be any string of characters not containing unbalanced parentheses, unpaired quotes, or commas not surrounded by quotes or parentheses. .sp .ti -5 -\Formal parameter replacement in occurs even inside of quoted strings. For example, .ne 6 .be define (assert (cond), { if (~(cond)) call error ("assertion cond not valid"p)} assert (i < j) .ee would generate .ne 5 .be { if (~(i < j)) call error ("assertion i < j not valid"p)} .ee .sp .ti -5 -\During replacement of an identifier defined without a formal parameter list, an actual parameter list will never be accessed. For example, .be .ne 5 define (ARRAYNAME, table1) ARRAYNAME (i, j) = 0 .ee would generate .be table1 (i, j) = 0 .ee .sp .ti -5 -\The number of actual and formal parameters need not match. Excess formal parameters will be replaced by null strings; excess actual parameters will be ignored. .sp .ti -5 -\A [bf define] statement affects only those identifiers following it. In the following example, STDIN would [bf not] be replaced by -11, unless a [bf define] statement for STDIN had occurred previously: .be .ne 2 l = getlin (buf, STDIN) define (STDIN, -11) .ee .sp .ti -5 -\A [bf define] statement applies to all lines following it in the input to 'rp', regardless of subroutine, procedure, and source file boundaries. .sp .ti -5 -\After replacement, the substituted text itself is examined for further defined identifiers. This allows such definition sequences as .be .ne 2 define (DELCOMMAND, LETD) define (LETD, 100) .ee to result in the desired replacement of .nh "100" for "DELCOMMAND". .hy Actual parameters are not reexamined until the entire replacement string is reexamined. .sp .ti -5 -\Identifiers may be redefined without error. The most recent definition supersedes all previous ones. Storage space used by superseded definitions is reclaimed. .sp .in -5 .pp Here are a few more examples of how defines can be used: .be .ne 22 [ul Before Defines Have Been Processed:] define (NO, 0) define (YES, 1) define (STDIN, -11) define (EOF, -2) define (RESET (flag), flag = NO) define (CHECK_FOR_ERROR (flag, msg), if (flag == YES) call error (msg) ) define (FATAL_ERROR_MESSAGE, "Fatal error -- run terminated"p) define (PROCESS_LINE, count = count + 1 call check_syntax (buf, count, error_flag) ) while (getlin (buf, STDIN) ~= EOF) { RESET (error_flag) PROCESS_LINE CHECK_FOR_ERROR (error_flag, FATAL_ERROR_MESSAGE) } .ne 9 [ul After Defines Have Been Processed:] while (getlin (buf, -11) ~= -2) { error_flag = 0 count = count + 1 call check_syntax (buf, count, error_flag) if (error_flag == 1) call error ("Fatal error -- run terminated"p) } .ee .SH "Undefine" The Ratfor [bf undefine] statement allows termination of the range of a [bf define] statement. The identifier named in the [bf undefine] statement is removed from the define table if it is present; otherwise, no action is taken. Storage used by the definition is reclaimed. For example, the statements .ne 6 .be define (xxx, a = 1) xxx undefine (xxx) xxx .ee would produce the following code: .ne 4 .be a = 1 xxx .ee .SH "Include" The Ratfor [bf include] statement allows you to include arbitrary files in a Ratfor program (much like the COBOL [bf copy] verb). The syntax of an [bf include] statement is as follows: .be .ne 1 include "" .ee If the file name is six or fewer characters in length and contains only alphanumeric characters, the quotes may be omitted. For the sake of uniformity, we suggest that the quotes always be used. .pp When 'rp' encounters an [bf include] statement, it begins taking input from the file specified by . When the end of the included file is encountered, 'rp' resumes reading the preempted file. Files named in [bf include] statements may themselves contain [bf include] statements; this nesting may continue to an arbitrary depth (which, by the way, is arbitrarily limited to five). .pp For an example of [bf include] at work, assume the existence of the following files: .be .ne 14 f1: .in +5 include "f2" i = 1 include "f3" .in -5 .sp f2: .in +5 include "f4" m = 1 .in -5 .sp f3: .in +5 j = 1 .in -5 .sp f4: .in +5 k = 1 .in -5 .ee If "f1" were the original file, the following text is what would actually be processed: .be .ne 4 k = 1 m = 1 i = 1 j = 1 .ee .MH "Ratfor Declarations" .pp There are several declarations available in Ratfor in addition to those usually supported in Fortran. They provide a way of conveniently declaring data structures not available in Fortran, assist in supporting separate compilation, allow declaration of local variables within compound statements, and allow the declaration of internal procedures. Declarations in Ratfor may be intermixed with executable statements. .SH "String" The [bf string] statement is provided as a shorthand way of creating and naming EOS-terminated strings. The structure and use of an EOS-terminated string is described in the section on Subsystem Conventions. Here it is sufficient to say that such a string is an integer array containing one character per element, right justified and zero filled, and ending with a special value (EOS) designating the "end of string." Since Fortran has no construct for specifying such a data structure, it must either be declared manually, as a Ratfor string constant, or by the Ratfor [bf string] statement. .pp The [bf string] statement is a declaration that creates a named string in an integer array using a Fortran [bf data] statement. The syntax of the [bf string] statement is as follows: .be .ne 1 string .ee where is the Ratfor identifier to be used in naming the string and specifies the string's contents. As you might expect, either single or double quotes may be used to delimit . In either case, only the characters between the quotes become part of the string; the quotes themselves are not included. .pp [bf String] statements are quite often used for setting up constant strings such as file names or key words. For instance, .be .ne 3 string file_name "//mydir/myfile" string change_command "change" string delete_command "delete" .ee define such character arrays. .SH "Stringtable" The [bf stringtable] statement creates a rather specialized data structure -- a marginally indexed array of variable length strings. This data structure provides the same ease of access as an array, but it can contain entries of varying sizes. A [bf stringtable] declaration defines two data items: a marginal index and a table body. The marginal index is an integer array containing indices into the table body. The first element of the marginal index is the number of entries following in the marginal index. Subsequent elements of the marginal index are pointers to the beginning of items in the table body. Since the beginning of the table body is always the beginning of an item, the second entry of the marginal index is always 1. .pp The syntax of a [bf stringtable] declaration is as follows: .be string_table , , [ / ] { / } .ee and
are identifiers that will be declared as the marginal index and table body, respectively. is a comma-separated list of single-character constants (with a "c" string format indicator), integers, or EOS-terminated character strings (with .ul no string format indicator -- a little inconsistency here). The values contained in an are stored contiguously in
with no separator values (save for an EOS at the end of each EOS-terminated string). An entry is made in the marginal index containing the position of the first word of each . .pp For example, assume that you have a program in which you wish to obtain one of three integer values based on an input string. You want to allow an arbitrary number of synonyms in the input (like "add", "insert", etc.). .be string_table cmdpos, cmdtext, / ADD, "add" _ / ADD, "insert" _ / CHANGE, "change" _ / CHANGE, "update" _ / DELETE, "delete" _ / DELETE, "remove" .ee This declaration creates a structure something like the following: .ne 16 .be cmdpos cmdtext 1: 6 2: 1 1: ADD, 'a'c, 'd'c, 'd'c, EOS 3: 6 6: ADD, 'i'c, 'n'c, 's'c, 'e'c, 'r'c, 't'c, EOS 4: 14 14: CHANGE, 'c'c, 'h'c, 'a'c, 'n'c, 'g'c, 'e'c, EOS 5: 22 22: CHANGE, 'u'c, 'p'c, 'd'c, 'a'c, 't'c, 'e'c, EOS 6: 29 29: DELETE, 'd'c, 'e'c, 'l'c, 'e'c, 't'c, 'e'c, EOS 7: 36 36: DELETE, 'r'c, 'e'c, 'm'c, 'o'c, 'v'c, 'e'c, EOS .ee .pp There are several routines in the Subsystem library that can be used to search for strings in one of these structures. You can find details on the use of these procedures in the reference manual/'help' entries for 'strlsr' and 'strbsr'. .SH "Linkage" The sole purpose of the [bf linkage] declaration is to circumvent problems with transforming Ratfor identifiers to Fortran identifiers when compiling program modules separately. To relax the restriction that externally visible names (subroutine, function, and common block names) must contain no more than six characters, each separately compiled module must begin with an identical [bf linkage] declaration containing the names of .ul all external symbols -- subroutine names, function names, and common block names (the identifiers inside the slashes -- not the variable names). Except for text substitution statements, the [bf linkage] declaration .ul must be the first statement in each module. The order of names in the statement .ul is significant -- as a general rule, you should [bf include] the same file containing the [bf linkage] declaration in each module. .pp [bf Linkage] looks very much like a Fortran type declaration: .be linkage identifier1, identifier2, identifier3 .ee Each of the identifiers is an external name (i.e. subroutine, function, or common block name). If this statement appears in each source module, .ul with the identifiers in exactly the same order, it is guaranteed that in all cases, each of these identifiers will be transformed into the .ul same unique Fortran identifier. For Subsystem-specific information on the mechanics of separate compilation, you can see the section in the applications notes devoted to this topic. .SH "Local" With the [bf local] declaration, you can indicate that certain variables are "local" to a particular compound statement (or block) just as in Algol. [bf Local] declarations are most often used inside internal procedures (which are described later), but they can appear in any compound statement. .pp [cc]mc | The type declarations for local variables must be preceded by a [cc]mc [bf local] declaration containing the names of all variables that are to be local to the block: .be local i, j, a integer i, j real a .ee The [bf local] statement must precede the first appearance of a variable inside the block. [cc]mc | While this isn't the greatest syntax in the world, it is easy to implement local variables in this fashion. [cc]mc .pp Scope rules similar to those of most block-structured languages apply to nested compound statements: A local variable is visible to all blocks nested within the block in which it is declared. Declaration of a local variable obscures a variable by the same name declared in an outer block. .pp There are several cautions you must observe when using local variables. 'Rp' is currently not well-versed in the semantics of Fortran declarations and therefore cannot diagnose the incorrect use of [bf local] declarations. Misuse can then result in semantic errors in the Fortran output that are often not caught by the Fortran compiler. If the declaration of a variable within a block appears before the variable is named in a [bf local] declaration, 'rp' will not detect the error, and an "undeclared variable" error will be generated in the Fortran. External names (i.e. function, subroutine, and common block names) must never be named in a [bf local] declaration, unless you want to declare a local variable of the same name. Finally, the formal parameters of internal procedures should never appear in a [bf local] declaration in the body of the procedure, again, unless you want to declare a local variable of the same name. .pp Here is an example showing the scopes of variables appearing in a [bf local] declaration: .ne 14 .be ### level 0 subroutine test integer i, j, k { ### level 1 local i, m; integer i, m # accessible: level 0 j, k; level 1 i, m { ### level 2 local m, k; real m, k # accessible: level 0 j; level 1 i; level 2 m, k } } end .ee .bp .MH "Ratfor Control Statements" As was said by Kernighan and Plauger in [ul Software Tools,] except for the control structures, "Ratfor is Fortran." The additional control structures just serve to give Fortran the capabilities that already exist in Algol, Pascal, and PL/I. .SH "Compound Statements" Ratfor allows the specification of a compound statement by surrounding a group of Ratfor statements with braces ("{}"), just like .nh [bf begin - end] in Algol or Pascal, or [bf do - end] .hy in PL/I. A compound statement may appear anywhere a single statement may appear, and is considered to be equivalent to a single statement when used within the scope of a Ratfor control statement. .pp There is normally no need for a compound statement to appear by itself -- compound statements usually appear in the context of a control structure -- but for completeness, here is an example of a compound statement. .be .ne 5 { # end of line -- set to beginning of next line line = line + 1 col = 1 end_of_line = YES } .ee .SH "If - Else" The Ratfor [bf if] statement is much more flexible than its Fortran counterpart. In addition to allowing a compound statement as an alternative, the Ratfor [bf if] includes an optional [bf else] statement to allow the specification of an alternative statement. Here is the complete syntax of the Ratfor [bf if] statement: .be if () [else ] .ee is an ordinary Fortran logical expression. If is true, will be executed. If is false and the [bf else] alternative is specified, will be executed. Otherwise, if is false and the [bf else] alternative has not been specified, no action occurs. .pp Both and may be compound statements or may be further [bf if] statements. In the case of nested [bf if] statements where one or more [bf else] alternatives are not specified, each [bf else] is paired with the most recently occurring [bf if] that has not already been paired with an [bf else.] .pp Although deep nesting of [bf if] statements hinders understanding, one situation often occurs when it is necessary to select one and only one of a set of alternatives based on several conditions. This can be nicely represented with a chain of .sb [bf if - else if - else if . . . else] .xb statements. For example, .be .ne 8 if (color == RED) call process_red else if (color == BLUE | color == GREEN) call process_blue_green else if (color == YELLOW) call process_yellow else call color_error .ee could be used to select a routine for processing based on color. .SH "While" The Ratfor [bf while] statement allows the repetition of a statement (or compound statement) as long as a specified condition is met. The Ratfor [bf while] loop is a "test at the top" loop exactly like the Pascal [bf while] and the PL/I [bf do while.] The [bf while ] statement has the following syntax: .be .ne 2 while () .ee If is false, control passes beyond the loop to the next statement in the program; if is true, is executed and is retested. As should be expected, if is false when the [bf while] is first entered, will be executed [ul zero] times. .pp The [bf while] statement is very handy for controlling such things as skipping blanks in strings: .be .ne 2 while (str (i) == BLANK) i = i + 1 .ee And of course, may also be a compound statement: .be .ne 4 while (getlin (buf, STDIN) ~= EOF) { call process (buf) call output (buf) } .ee .SH "Repeat" The Ratfor [bf repeat] loop allows repetitive execution of a statement until a specified condition is met. But, unlike the [bf while] loop, the test is made at the bottom of the loop, so that the controlled statement will be executed at least once. The [bf repeat] loop has syntax as follows: .be .ne 2 repeat [until ()] .ee When the [bf repeat] statement is encountered, is executed. If is found to be false, is reexecuted and the is retested. Otherwise control passes to the statement following the [bf repeat] loop. If the [bf until] portion of the loop is omitted, the loop is considered an "infinite repeat" and must be terminated within (usually with a [bf break] or [bf return] statement). Pascal users should note that the scope of the Ratfor [bf repeat] is only a single (which of course may be compound). .pp [bf Repeat] loops, as opposed to [bf while] loops, are used when the controlled statement must be evaluated at least once. For example, .be .ne 3 repeat call get_next_token (token) until (token ~= BLANK_TOKEN) .ee The "infinite repeat" is often useful when a loop must be terminated "in the middle:" .be .ne 7 repeat { call get_next_input (inp) call check_syntax (inp, error_flag) if (error_flag == NO) return call syntax_error (inp) # go back and get another } .ee .SH "Do" Ratfor provides access to the Fortran [bf do] statement. The Ratfor [bf do] statement is identical to the Fortran [bf do ] except that it does not use a statement label to delimit its scope. The Ratfor [bf do ] statement has the following syntax: .be .ne 2 do .ee is the normal Fortran notation for the limits of a [bf do,] such as "i = 1, 10" or "j = 5, 20, 2". The same restrictions apply to as apply to the limits in the Fortran [bf do.] is any Ratfor statement (which may be compound). .pp The Ratfor [bf do] statement is just like the standard Fortran one-trip [bf do] loop -- will be executed at least once, regardless of the limits. Also, the value of the [bf do] control variable is not defined on exit from the loop. .pp The [bf do] loop can be used for array initialization and other such things that can never require "zero trips", since it produces [ul slightly] more efficient object code than the [bf for] statement (which we will get to next). .be .ne 2 do i = 1, 10 array (i) = 0 .ee .pp One slight irregularity in the Ratfor syntax occurs when appears on the same line as the [bf do.] Since 'rp' knows very little about Fortran, it assumes that the continue until a statement delimiter. This means that the must be followed by a semicolon if is to begin on the same line. This often occurs when a compound statement is to be used: .be .ne 4 do i = 1, 10; { array_1 (i) = 0 array_2 (i) = 0 } .ee .SH "For" The Ratfor [bf for] statement is an all-purpose looping construct that takes the best features of both the [bf while] and [bf do] statements, while allowing more flexibility. The syntax of the [bf for] statement is as follows: .be .ne 2 for (; ; ) .ee When the [bf for] is executed, the statement represented by is executed. Then, if is true, is executed, followed by the statement represented by . Then, is retested, etc. Any or all of , , or may be omitted; the semicolons, however, must remain. If or is omitted, no action is performed in their place. If is omitted, an "infinite loop" is assumed. (Both or may be compound statements). .pp As you can see, the [bf for] loop with and omitted is identical to the [bf while] loop. With the addition of and , a zero-trip [bf do] loop can be constructed. For instance, .be .ne 4 for (i = 1; i <= 10; i += 1) { array_1 (i) = 0 array_2 (i) = 0 } .ee is identical to the last [bf do] example, but given a certain combination of limits, the [bf for] loop would execute zero times while the [bf do] loop would execute it once. .pp The [bf for] loop can do many things not possible with a [bf do] loop, since the [bf for] loop is not constrained to the ascending incrementation of an index. As an example, assume a list structure in which "list" contains the index of the first item in a list, and the first position in each list item contains the index of the next. The [bf for] statement could be used to serially examine the list: .be .ne 3 for (ptr = list; ptr ~= NULL; ptr = array (ptr)){ [ examine the item beginning at array (ptr + 1) ] } .ee .SH "Break" The [bf break] statement allows the early termination of a loop. The statement .be break [] .ee will cause the immediate termination of loops, where , if specified, is an integer in the range 1 to the depth of loop nesting at the point the [bf break] statement appears. Where is omitted, only the innermost loop surrounding the [bf break] is terminated. .pp In the following example, the [bf break] statement will cause the termination of the inner [bf for] loop if a blank is encountered in 'str': .be .ne 9 while (getlin (str, STDIN) ~= EOF) { for (i = 1; str (i) ~= EOS; i += 1) if (str (i) == BLANK) break str (i) = EOS # output just the first word call putlin (str, STDOUT) call putch (NEWLINE, STDOUT) } .ee Replacing the [bf break] statement with "break 1" would have exactly the same effect. However, replacing it with "break 2" would cause termination of both the inner [bf for] and outer [bf while] loops. Unless this fragment is nested inside other loops, a value greater than 2 would be an error. .SH "Next" The [bf next] statement is very similar to the [bf break] statement, except that a statement of the form .be next [] .ee causes termination of - 1 nested loops (zero when is omitted). Execution then resumes with the [ul next] iteration of the innermost active loop. , if specified, is again an integer in the range 1 to the depth of loop nesting that specifies which loop (from inside out) is to begin its next iteration. .pp In this example, the [bf next] statement will cause the processing to be skipped when an array element with the value "UNUSED" is encountered. .be .ne 8 for (i = 1; i <= 10; i += 1) for (j = 1; j <= 10; j += 1) { if (array (i, j) == UNUSED) next # process array (i, j) } .ee When an array element with the value "UNUSED" is encountered, execution of the [bf next] statement causes the portion of the innermost [bf for] statement, "j += 1", to be executed before the next iteration of the inner loop begins. You should note that when used with a [bf for] statement, [bf next] always skips to the part of the appropriate [bf for] loop. .pp If the statement "next 2" had been used in place of "next", the inner [bf for] loop would have been terminated, and the "i += 1" of the outer [bf for] loop would have been executed in preparation for its next iteration. .SH "Return" The Ratfor [bf return] statement normally behaves exactly like the Fortran [bf return] statement in all but one case. In this case, Ratfor allows a parenthesized expression to follow the keyword [bf return] inside a function subprogram. The value of this expression is then assigned to the function name as the value of the function before the return is executed. This is just another shorthand and does not provide any additional functionality. .pp Normally in a Fortran function subprogram, you place an assignment statement that assigns a value to the function name before the [bf return] statement, like this: .be integer function calc (x, y, z) ... calc = x + y - z return ... .ee If you like, Ratfor allows you to express the same actions with one line less code: .be integer function calc (x, y, z) ... return (x + y - z) ... .ee This segment performs exactly the same function as the preceding segment. .SH "Select" The Ratfor [bf select] statement allows the selection of a statement from several alternatives, based either on the value of an integer variable or on the outcome of several logical conditions. A [bf select] statement of the form .ne 14 .be select when () when () ... when () [ifany ] [else ] .ee (where is a comma-separated list of logical expressions) performs almost the same function as a chain of [bf if - else if . . . else] statements. Each is evaluated in turn, and [cc]mc | when the first true expression is encountered, the corresponding statement [cc]mc is executed. If any [bf when] alternative is selected, the statement in the [bf ifany] part is executed. If none of the [bf when] alternatives are selected, the statement in the [bf else] part is executed. .pp Although its function is very similar to an [bf if - else] chain, a [bf select] statement has two distinct advantages. First, it allows the "ifany" alternative -- a way to implement a rather frequently encountered control structure without repeated code or procedure calls. Second, it places all the logical expressions in the same basic optimization block, so that even a dumb Fortran compiler can optimize register loads and stores. .pp For example, assume that we want to check to see if the variable 'color' contains a valid color, namely 'RED', 'YELLOW', 'BLUE', or 'GREEN'. If it does, we want to executed one of the three subroutines 'process_red', 'process_yellow', or 'process_blue_green' and set the flag 'color_valid' to YES. Otherwise, we want to set the 'color_valid' to NO. A [bf select] statement performs this trick nicely, with no repeated code: .ne 13 .be select when (color == RED) call process_red when (color == YELLOW) call process_yellow when (color == BLUE, color == GREEN) call process_blue_green ifany color_valid = YES else color_valid = NO .ee .pp The second variant of the select statement allows the selection of a statement based on the value of an integer (or character) expression. It has almost exactly the same syntax as the logical variant: .be select () when () when () ... when () [ifany ] [else ] .ee Using this variant, a statement is selected when one of its corresponding integer expressions has the same value as the following the 'select'. The [bf ifany] and [bf else] clause behave as they do in the logical variant. The most visible difference, though, is that the order of evaluation of the integer expressions is not specified. If two values in two expression lists are identical, it is difficult to say which of the statements will be executed; it can only be said that one and only one will be executed. .pp The integer variant offers one further advantage. If elements in the expression lists are integer or single-character constants, 'rp' will generate Fortran computed [bf goto] statements, rather than Fortran [bf if] statements, where possible. This code is usually considerably faster and more compact than the code generated by [bf if] statements. .pp The example given for the logical variant of [bf select] would really be much more easily done with the integer variant: .ne 13 .be select (color) when (RED) call process_red when (YELLOW) call process_yellow when (BLUE, GREEN) call process_blue_green ifany color_valid = YES else color_valid = NO .ee .pp As a final example of [bf select,] the following program fragment selects an insert, update, delete, or print routine based on the input codes "i", "u", "d" or "p": .ne 19 .be while (getlin (buf, STDIN) ~= EOF) select (buf (1)) when ('i'c, 'I'c) # insert record call insert_record when ('u'c, 'U'c) { # update record call delete_record call insert_record } when ('d'c, 'D'c) # delete record call delete_record when ('p'c, 'P'c) # print record ; ifany # always print after command call print_record else # illegal input call command_error .ee This example shows the use of both a compound statement within an alternative (the "update" action deletes the target record and then inserts a new version), and a null statement consisting of a single semicolon. .SH "Procedure" Procedures are a convenient and useful structuring mechanism for programs, but in Fortran there often reasons for restricting the unbridled use of procedures. Among these reasons are (1) the run-time expense of procedure calls, and argument and common block addressing; (2) external name space congestion; and (3) difficulty in detecting errors in parameter and common-block correspondence. Ratfor attempts to address these problems by allowing declaration of procedures within Fortran subprograms that are inexpensive to call (an assignment and two [bf gotos),] are not externally visible, and allow access to global variables. In addition, when correctly declared, Ratfor internal procedures can call each other recursively without requiring recursive procedures in the host Fortran. .pp Currently, Ratfor internal procedures do not provide the same level of functionality as Fortran subroutines and functions: internal procedure parameters must be scalars and are passed by value, internal procedures cannot be used as functions (they cannot return values), and no automatic storage is available with recursive integer procedures. But even with these restrictions, internal procedures can significantly improve the readability and modularity of Ratfor code. .pp Internal procedures are declared with the Ratfor [bf procedure] statement. Internal procedures may be declared anywhere in a program, but a declaration must appear before any of its calls. Here is an example of a non-recursive procedure declaration: .ne 10 .be # putchar --- put a character in the output string procedure putchar (ch) { character ch str (i) = ch i += 1 } .ee This procedure has one parameter, "ch", which must appear in a type declaration inside the procedure. .pp Internal procedures always exit by falling through the end of the compound statement. A [bf return] statement in an internal procedure will return from the Fortran subprogram in which the internal procedure is declared. .pp After the above declaration, "putchar" can be subsequently called in one of two ways: .be putchar ('='c) -or- call putchar ('='c) .ee The second form is preferable, so that a procedure can be converted to a subroutine, and vice-versa. The number of parameters in the call must always match the number of parameters in the declaration. If parameter list is omitted in the declaration, then it also must be omitted in its calls. .pp If "putchar" were recursive, the declaration would be .be procedure putchar (ch) recursive 128 .ee The value "128" is an integer constant that is the maximum number of recursive calls to "putchar" outstanding at any one time. .pp Since internal procedures may be mutually recursive, and since they must be declared textually before they are used, procedures may be declared "forward" by separating the procedure declaration from its body. Here is "putchar" declared using a "forward" declaration: .be procedure putchar (ch) forward ... # putchar --- put a character in the output string procedure putchar { character ch str (i) = ch i += 1 } .ee As you can see, the parameters must appear in the "forward" declaration; they may appear in the body declaration, but are ignored. For maximum efficiency, all internal procedures should be presented in a "forward" declaration. The procedure bodies should then be declared after the final [bf return] or [bf stop] [cc]mc | statement in the body of the Fortran subprogram, but before the terminating [bf end] statement (then the [cc]mc program never has to jump around the procedure body). .pp In general, a [bf procedure] declaration contains five parts: the word "procedure", the procedure name, an optional list of formal parameters, an optional "recursive " part, and either a compound statement or the word "forward". An internal procedure call consists of three parts: optionally the word "call", the procedure name, and an optional parameter list.