home *** CD-ROM | disk | FTP | other *** search
- Path: sparky!uunet!olivea!charnel!sifon!thunder.mcrcim.mcgill.edu!snorkelwacker.mit.edu!ai-lab!life.ai.mit.edu!burley
- From: burley@apple-gunkies.gnu.ai.mit.edu (Craig Burley)
- Newsgroups: comp.lang.fortran
- Subject: Understanding ENTRY and Recursion (was Re: ENTRY in main)
- Message-ID: <BURLEY.92Nov23121000@apple-gunkies.gnu.ai.mit.edu>
- Date: 23 Nov 92 20:10:00 GMT
- References: <By4xpo.FG@iapa.uucp%mailhost.ecn.uoknor.edu>
- <1992Nov23.114807.4734@monu6.cc.monash.edu.au>
- Organization: Free Software Foundation 545 Tech Square Cambridge, MA 02139
- Lines: 245
- NNTP-Posting-Host: apple-gunkies.gnu.ai.mit.edu
- In-reply-to: map@hal.maths.monash.edu.au's message of Mon, 23 Nov 1992 11:48:07 GMT
-
- In article <1992Nov23.114807.4734@monu6.cc.monash.edu.au> map@hal.maths.monash.edu.au (Michael Page) writes:
-
- ENTRY statements are a form of global GOTO statement and, much as I
- detest and discourage GOTOs, they do have an occasional application,
- in particular for error handling. I'm not encouraging their use but
- they do seem to have a place in the language.
-
- No, ENTRY statements are not a form of a global GOTO statement. You
- misunderstand their use entirely, and any success you've had using them
- that way is almost certainly nonportable and/or accidental.
-
- > Also, wouldn't this be considered recursion, since a routine subordinate
- > to main (effectively) calls main?
-
- Recursion is already possible (and legal?) in standard FORTRAN by simply
- having two subroutines which are different only in name that call each
- other. I have no idea what this does to the stack, though.
-
- No, recursion is not legal in standard FORTRAN. You can't make it so by
- duplicating subroutines and renaming one of them.
-
- OK, here is a way to understand what ENTRY means, by way of recoding a
- sample use of ENTRY into Fortran code that doesn't use it, but still
- preserves all the semantics and meaning:
-
- SUBROUTINE X(ARG1, ARG2)
- COMMON /FOO/ A, B, C
- INTEGER INVOKE
- SAVE INVOKE
- DATA INVOKE/0/
- C
- INVOKE = INVOKE + 1
- ...do real stuff #1...
- RETURN
- C
- ENTRY Y(ARG2, ARG3)
- INVOKE = INVOKE + 1
- ...do real stuff #2...
- RETURN
- C
- ENTRY Z(INVOQ)
- INVOQ = INVOKE
- END
-
- Note that X and Y share an argument, ARG2, so it can be referenced,
- defined, or undefined when X or Y is called -- but not Z. Meanwhile,
- ARG1 can't be touched in the code path used by a call to Y, nor can
- ARG3 be touched by Z's code path. Although I've shown "real stuff" #s
- 1 and 2 as separate things, they can GOTO back and forth between each
- other (to share code) as long as these rules are followed.
-
- Z just returns in its first argument the total number of times X and Y have
- been called prior to Z being called.
-
- Note for recursion enthusiasts: once X, Y, or Z is called, the running
- program may not call _any_ of X, Y, or Z directly or indirectly until that
- first X, Y, or Z returns to its caller. This means that "real stuff #1"
- cannot CALL Z(J), nor can it CALL FROB if FROB does CALL Z(J).
-
- Now, here is the above code recoded without ENTRY in an almost entirely
- standard way:
-
- SUBROUTINE unique-name(WHICH, ARG1, ARG2, ARG3, INVOQ)
- INTEGER WHICH
- COMMON /FOO/ A, B, C
- INTEGER INVOKE
- SAVE INVOKE
- DATA INVOKE/0/
- C
- GOTO (10, 20, 30) WHICH
- C
- 10 INVOKE = INVOKE + 1
- ...do real stuff #1...
- RETURN
- C
- 20 INVOKE = INVOKE + 1
- ...do real stuff #2...
- RETURN
- C
- 30 INVOQ = INVOKE
- END
-
- SUBROUTINE X(ARG1, ARG2)
- CALL unique-name(1, ARG1, ARG2, 0., 0)
- END
-
- SUBROUTINE Y(ARG2, ARG3)
- CALL unique-name(2, 0., ARG2, ARG3, 0)
- END
-
- SUBROUTINE Z(INVOQ)
- CALL unique-name(3, 0., 0., 0., INVOQ)
- END
-
- The only nonstandard thing is "unique-name", which the translation must
- substitute with a name that is unique across the entire program (easy
- for compilers, usually -- GNU Fortran just makes a name unique across
- the source file and doesn't make it global, which achieves the same thing).
-
- It might look wrong for X and Y to pass 0 for INVOQ, since INVOQ is modified
- by the unique-name procedure, but it isn't -- the variable is only modified
- in the case where Z calls unique-name, else the _original_ example was as
- nonconforming as this recoding.
-
- f2c handles ENTRY similar to the above method, though since it has the
- luxury of translating into C instead of Fortran, it can do some things more
- simply (e.g. pass NULL instead of 0. for missing arguments, especially nice
- when the missing argument is a large array and, for pure standards
- conformance, I think that'd mean a space-wasting unused array would have
- to be passed to make the code valid Fortran, while NULL is suitable for
- valid C).
-
- Recursion is another story entirely. If you've been doing something
- like
-
- SUBROUTINE X(ARG1, ARG2, ARG3)
- ...
- ENTRY XERROR
- PRINT *,'Error in X'
- ARG1 = 0.
- END
-
- and calling XERROR when an error is detected in X or by any of the
- procedures it calls, you are attempting recursion, and you're also asking
- from trouble in other ways (even if X isn't active).
-
- The recursion problem with the above is that the compiler might well
- have made lots of internal (invisible) data structures static, meaning
- that even the PRINT statement might not work properly on all systems
- (even though it might on yours).
-
- The other problem is that XERROR doesn't take ARG1 as an argument, so the
- assignment is non-conforming. If your machine happens to, when X is
- active at the point XERROR is invoked, actually set X's ARG1 to 0. as a
- result of the assignment, you're lucky or incredibly knowledgable about writing
- very nonportable code for your particular machine. But you're not using
- ENTRY correctly from a standard-conformance point of view.
-
- Instead, you have to do something like:
-
- SUBROUTINE X(ARG1, ARG2, ARG3)
- ...
- 999 PRINT *,'Error in X'
- ARG1 = 0.
- END
-
- Then, anyplace in the ..., GOTO 999 to do the abort, and anyplace you would
- invoke a function or subroutine in the ... that might want to do the abort
- directly, recode the function as a subroutine and pass *999 as an alternate-
- return argument, to be used by the called subroutine or any of the procedures
- it calls (i.e. apply this technique recursively :-). See my earlier post
- about ENTRY for a clearer description.
-
- Also realize that this is not a conforming program in ANSI FORTRAN 77
- (or in Fortran 90 for that matter):
-
- PROGRAM FOO
- CALL BAR
- END
-
- SUBROUTINE BAR
- LOGICAL FIRST
- SAVE FIRST
- DATA FIRST/.TRUE./
- C
- IF (FIRST) THEN
- FIRST = .FALSE.
- CALL BLETCH
- END IF
- C
- PRINT *,'BAR'
- END
-
- SUBROUTINE BLETCH
- PRINT *,'BLETCH'
- CALL BAR
- END
-
- If you're lucky, the above program might print
-
- BLETCH
- BAR
- BAR
-
- but it is also perfectly standard-conforming for your system to delete
- all your files as well, since you're program is not standard-conforming
- due to its attempt to recurse. :-)
-
- Also note that even the following is not permitted by the standard:
-
- PRINT *,FOO(ICHAR('2'))
- END
-
- REAL FUNCTION FOO(I)
- CHARACTER*20 IFILE
- IFILE = CHAR(I) // '.3'
- READ (10, IFILE) FOO
- 10 FORMAT (F3.1)
- END
-
- (Assuming I've got the format and other such sundry details right....)
-
- The above is not _overt_ recursion, but is nevertheless disallowed by
- the standard because it has _implicit_ recursion of the Fortran I/O subsystem.
- In particular, FOO is invoked after the start of I/O, and then FOO itself
- reinvokes the I/O subsystem to do the internal-file READ to translate
- "2.3" into a REAL value in FOO.
-
- You might say, "well why didn't they require pre-evaluation of the arguments
- to the I/O statement?" Good question. First, I'm not sure offhand if
- this would be valid:
-
- WRITE (*,*,ERR=20) (FOO(I), FOO(I), J=1,100)
- 20 END
-
- REAL FUNCTION FOO(I)
- FOO = FLOAT (I)
- I = I + 1
- END
-
- If it is valid (which I think it might be, actually), it wouldn't work
- if preevaluation was required (I should not always end up with the value
- 201 or whatever it gets, if there's an error writing to *).
-
- But, more specifically, how would this work?
-
- READ *, I, FOO(I)
- END
-
- Here, only after the value I is read, and while the I/O is still active,
- do we know what to pass to FOO for preevaluation.
-
- Believe me, this kind of thing gives all sorts of problems to those of
- us who implement Fortran compiler systems, especially on weird
- architectures or under other such constraints!
-
- I don't remember whether Fortran 90 lifts the restriction on recursive
- invocation of the I/O subsystem, offhand. Sure would be nice, since it
- seems possible, and it's hard for programmers to keep themselves from
- violating the rule (especially given how it is so common to insert
- PRINT statements to debug code).
- --
-
- James Craig Burley, Software Craftsperson burley@gnu.ai.mit.edu
- Member of the League for Programming Freedom (LPF) lpf@uunet.uu.net
-