Macros That Work

Macros That Work

Macros That Work William Clinger Jonathan Rees Department of Computer Science Artificial Intelligence Laboratory University of Oregon Massachusetts Institute of Technology Abstract written, and is simple to use. The algorithm is nearly as fast as algorithms in current use. This paper describes a modified form of Kohlbecker’s algo- This introduction illustrates several problems common rithm for reliably hygienic (capture-free) macro expansion to existing macro systems, using examples written in C and in block-structured languages, where macros are source-to- Scheme [10]. With one exception, all of these problems are source transformations specified using a high-level pattern solved automatically and reliably by the algorithm presented language. Unlike previous algorithms, the modified algo- in this paper. rithm runs in linear instead of quadratic time, copies few The original preprocessor for C allowed the body of a constants, does not assume that syntactic keywords (e.g. if) macro definition to be an arbitrary sequence of characters. are reserved words, and allows local (scoped) macros to refer It was therefore possible for a macro to expand into part of to lexical variables in a referentially transparent manner. a token. For example, Syntactic closures have been advanced as an alternative to hygienic macro expansion. The problem with syntactic #define foo "salad closures is that they are inherently low-level and therefore printf(foo bar"); difficult to use correctly, especially when syntactic keywords would expand into printf("salad bar");. We may say are not reserved. It is impossible to construct a pattern- that this macro processor failed to respect the integrity of based, automatically hygienic macro system on top of syn- lexical tokens. This behavior caused so many problems that tactic closures because the pattern interpreter must be able it has been banished from ANSI C except when special op- to determine the syntactic role of an identifier (in order to erators are used for the specific purpose of violating the close it in the correct syntactic environment) before macro integrity of lexical tokens. expansion has made that role apparent. Similar problems arise in macro systems that allow the Kohlbecker’s algorithm may be viewed as a book-keeping body of a macro to expand into an arbitrary sequence of technique for deferring such decisions until macro expansion tokens. For example, is locally complete. Building on that insight, this paper uni- fies and extends the competing paradigms of hygienic macro #define discriminant(a,b,c) b*b-4*a*c expansion and syntactic closures to obtain an algorithm that 2*discriminant(x-y,x+y,x-y)*5 combines the benefits of both. Several prototypes of a complete macro system for expands into 2*x+y*x+y-4*x-y*x-y*5, which is then parsed Scheme have been based on the algorithm presented here. as (2*x)+(y*x)+y-(4*x)-(y*x)-(y*5) 1 Introduction instead of the more plausible A macro is a source-to-source program transformation spec- ified by programmers using a high-level pattern language. 2*(x+y)*(x+y)-4*(x-y)*(x-y)*5. Macros add a useful degree of syntactic extensibility to a We may say that systems such as the ANSI C preprocessor programming language, and provide a convenient way to fail to respect the structure of expressions. Experienced C abbreviate common programming idioms. programmers know they must defend against this problem Macros are a prominent component of several popular by placing parentheses around the macro body and around programming languages, including C [7] and Common Lisp all uses of macro parameters, as in [11]. The macro systems provided by these languages suf- fer from various problems that make it difficult and in many #define discriminant(a,b,c) ((b)*(b)-4*(a)*(c)) cases impossible to write macros that work correctly regard- less of the context in which the macros are used. It is widely This convention is not enforced by the C language, however, believed that these problems result from the very nature of so the author of a macro who neglects this precaution is macro processing, so that macros are an inherently unreli- likely to create subtle bugs in programs that use the macro. able feature of programming languages [2]. Another problem with the discriminant macro occurs That is not so. A macro system based upon the algo- when the actual parameter for b is an expression that has a rithm described in this paper allows reliable macros to be side effect, as in discriminant(3,x--,2). Of the problems listed in this introduction, the inappropriate duplication of 1 side effects is the only one that is not solved automatically that fail to respect the correlation between bindings and uses by our algorithm. of names. One class involves macros that introduce bound One can argue that actual parameter expressions should (local) variables. Another class involves macros that contain not have side effects, but this argument runs counter to free references to variables or procedures. the C culture. In a fully block-structured language such Hygienic macro expansion [8] is a technology that main- as Scheme, the author of a macro can defend against actual tains the correlation between bindings and uses of names, parameters with side effects by introducing a local variable while respecting the structure of tokens and expressions. that is initialized to the value obtained by referencing the The main problem with hygienic macro expansion has been macro parameter. In the syntax we adopt for this paper, that the algorithms have required O(n2) time, where n is the which differs slightly from syntax that has been proposed time required by naive macro expansion as in C or Common for Scheme [3], a correct version of the discriminant macro Lisp. Although this is the worst case, it sometimes occurs can be defined by in practice. The purpose of this paper is to build on Kohlbecker’s (define-syntax discriminant algorithm for hygienic macro expansion by incorporating (syntax-rules ideas developed within the framework of syntactic closures ((discriminant ?a ?b ?c) [1]. Our result is an efficient and more complete solution => (let ((temp ?b)) to the problems of macro processing. Our algorithm runs (- (* temp temp) (* 4 ?a ?c)))))) in O(n) time and solves several additional problems associ- Unfortunately there is no equivalent to this macro in C, since ated with local (scoped) macros that had not been addressed blocks are not permitted as expressions. by hygienic macro expansion. In particular, our algorithm From the above example we see that macros must be able supports referentially transparent local macros as described to introduce local variables. Local variables, however, make below. macros vulnerable to accidental conflicts of names, as in If macros can be declared within the scope of lexical vari- ables, as in Common Lisp, then a seemingly new set of prob- #define swap(v,w) {int temp=(v); \ lems arises. We would like for free variables that occur on (v) = (w); (w) = temp;} the right hand side of a rewriting rule for a macro to be int temp = thermometer(); resolved in the lexical environment of the macro definition if (temp < lo_temp) swap(temp, lo_temp); instead of being resolved in the lexical environment of the use of the macro. Using let-syntax to declare local macros, Here the if statement never exchanges the values of temp for example, it would be nice if and lo temp, because of an accidental name clash. The macro writer’s usual defense is to use an obscure name and (let-syntax ((first hope that it will not be used by a client of the macro: (syntax-rules #define swap(v,w) {int __temp_obscure_name=(v); \ ((first ?x) => (car ?x)))) (v) = (w); (w) = __temp_obscure_name;} (second (syntax-rules Even if the macro writer is careful to choose an obscure ((second ?x) => (car (cdr ?x)))))) name, this defense is less than reliable. For example, the (let ((car "duesenberg")) macro may be used within some other use of a macro whose (let-syntax ((classic author happened to choose the same obscure name. This (syntax-rules problem can be especially perplexing to authors of recursive ((classic) => car)))) macros. (let ((car "yugo")) The most troublesome name clashes arise not from vari- (let-syntax ((affordable ables local to a macro, but from the very common case of a (syntax-rules macro that needs to refer to a global variable or procedure: ((affordable) => car)))) (let ((cars (list (classic) #define complain(msg) print_error(msg); (affordable)))) if (printer_broken()) { (list (second cars) char *print_error = "The printer is broken"; (first cars)))))))) complain(print_error); } could be made to evaluate to ("yugo" "duesenberg"). This would be the case if the references to car that are introduced Here the name given to the error message conflicts with the by the first, second, classic, and affordable macros re- name of the procedure that prints the message. In this ex- ferred in each case to the car variable within whose scope ample the compiler will report a type error, but program- the macro was defined. In other words, this example would mers are not always so lucky. evaluate to ("yugo" "duesenberg") if local macros were ref- In most macro systems there is no good way to work erentially transparent. around this problem. Programs would become less readable Our algorithm supports referentially transparent local if obscure names were chosen for global variables and pro- macros. We should note that these macros may not be refer- cedures, and the author of a macro probably does not have entially transparent in the traditional sense because of side the right to choose those names anyway. Requiring the client effects and other problems caused by the programming lan- of each macro to be aware of the names that occur free in guage whose syntax they extend, but we blame the language the macro would defeat the purpose of macros as syntactic for this and say that the macros themselves are referentially abstractions.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    8 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us