Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!uunet!husc6!necntc!necis!mrst!sdti!turner From: turner@sdti.UUCP (Prescott K. Turner) Newsgroups: comp.lang.c Subject: ANS C Macro Processing Message-ID: <167@sdti.UUCP> Date: Tue, 20-Oct-87 15:38:03 EDT Article-I.D.: sdti.167 Posted: Tue Oct 20 15:38:03 1987 Date-Received: Fri, 23-Oct-87 05:59:06 EDT Reply-To: turner%sdti@harvard.harvard.edu (Prescott K. Turner, Jr.) Organization: Software Development Technologies, Sudbury MA Lines: 158 Summary: Preventing "recursive death" is not simple. I have been working with C macro processing, as described in the draft proposed standard (henceforth the "standard"). In doing so, I have come across some features which may be of interest because they are vaguely specified or obscure. Examples are provided, since these aspects are not covered by the examples in the standard. The standard is clear in 3.8.3.4 about what is rescanned after macro replacement and parameter substitution. Most of the obscurities arise because (as discussed in the rationale) "the Committee agreed simply to turn off the definition of a macro for the duration of the expansion of that macro." After an attempt to understand the pertinent paragraph, it is my belief that the task of turning off a macro definition is not at all simple. The first example deals with the term "nested replacmement". A macro definition, when suppressed within the macro's replacment, continues suppressed in further nested replacements. But how do you tell whether the invocation of a function-like macro is nested? Is it the macro name that counts, or must the parenthesized argument list also be nested? The latter is perhaps a more intuitive interpretation of "nested replacement", but is not consistent with the way a macro invocation is suppressed (which is based only on where the name appears). It also leads to some obscurities which I won't get into here. So I prefer an interpretation where "if any other macro name is found during such a scan of the replacement list, and is expanded, the definition of the original macro is suppressed in the other macro's replacement". Here's a contrived example in which the definition of "nested replacment" makes a difference. in the context of #define S T #define T(x) S start with S(1) replacing S T(1) where S is suppressed in "T" replacing T(1) S *** *** I consider the replacement of "T(1)" by "S" as nested in the first replacement "S". Therefore both S and T are suppressed in the second replacement "S". (In the other interpretation, this would be further expanded to "T".) Other interesting cases are more complex. The above example is interesting because the invocation of T spans the boundary of S's suppression. What if T has S as an argument? The argument appears in a position where the definition of S is not directly suppressed. Is processing of the macro name S as an argument considered part of the "nested replacement" and therefore not expanded? I lean toward suppressing the definition of S in this context for the sake of simplicity. in the context of #define S T #define T(x) x start with S(S) replacing S T(S) where S is suppressed in "T" processing T(S) expanding the argument S as part of the processing of T (no expansion??) replacing T(S) S where S and T are suppressed in "S" The rest of my examples deal with "nonreplaced macro name preprocessing tokens". Fortunately, the standard is less open to varying interpretations than in the above cases. It also gives an example, from which the relevant piece is: in the context of #define z z[0] #define f(a) f(2 * (a)) start with f(f(z)) identify invocation f(f(z)) process argument f(z) identify invocation f(z) process argument z identify invocation z replace z z[0] rescan, with definition of z suppressed z[0] The "z" is marked "nonreplaced". replace f(z) f(2 * (z[0])) The "z" is marked "nonreplaced". rescan, with definition of f suppressed *** f(2 * (z[0])) The "f" and "z" are marked "nonreplaced". replace f(f(z)) f(2 * (f(2 * (z[0])))) The "z" and second "f" are marked "nonreplaced". rescan, with definition of f suppressed *** f(2 * (f(2 * (z[0])))) The "z" and "f"s are marked "nonreplaced". done Only the steps marked *** illustrate the point, because the sole reason "z" is not expanded is that it has been examined and not replaced. An area left unexplored by this example is that there may be other reasons for not expanding a macro name (in particular it may be a function-like macro not followed by "("). In case there are other reasons, in addition to the suppression of the macro definition, one would still consider the token "nonreplaced". For example: in the context of #define z(x) +z #define f(a) f(a(3)) start with f(z(1)) identify invocation f(z(1)) process argument z(1) identify invocation z(1) replace z(1) +z rescan, with definition of z suppressed +z *1* The "z" is marked "nonreplaced". replace f(z(1)) f(+z(3)) The "z" is marked "nonreplaced". rescan, with definition of f suppressed *2* f(+z(3)) The "f" and "z" are marked "nonreplaced". done The point of the above example is that at *1* "z" is marked "nonreplaced" even though no "(" is present. Subsequently at *2* "z(3)" is not expanded even though it was never rejected in this form. Macro expansion has other obscure yet surprising aspects. If z expands to y during argument processing, then during rescan y can have a replacement in which z expands again. As an example: in the context of #define z +y #define y(b) z #define f(a) f(a(3)) start with f(z) identify invocation f(z) process argument z identify invocation z replace z +y rescan, with definition of z suppressed replace f(z) f(+y(3)) rescan, with definition of f suppressed identify invocation y(3) process argument 3 replace y(3) f(+z) rescan starting at "+", with definitions of f and y suppressed identify invocation z replace z f(++y) rescan, starting at 2nd "+", with definitions of f, y, and z suppressed f(++y) done -- Prescott K. Turner, Jr. Software Development Technologies, Inc. 375 Dutton Rd., Sudbury, MA 01776 USA (617) 443-5779 UUCP:necntc!necis!mrst!sdti!turner