#define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be...

7
The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example: #define plus3(x) x+3 This replaces the code plus3(<exp>)” with the code “<exp> + 3Careful construction must be used: y = plus3(x)*5; will result in: y = x+3*5; This is not exactly what we intended! But: #define plus3(x) (x+3) will do the job. Another example: #define max(n,m) (n<m)?m:n Will cause problems if n is the expression ++x, it will be incremented twice! max(++x,y); will result in: (++x<y)? ++x : y; #define max(n,m) { int x = n,y=m; (x<y)?y:x; } Careful programming must be used with macros. As a general rule, use only expressions without side-effects in C macros or use lexically hygienic macros. ** Assuming x and y are not used elsewhere (the call max(5,x) will fail in this case). The problem can be resolved** this way:

Transcript of #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be...

Page 1: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

The C preprocessor can be used to define macros in C, macros are defined using a #define directive.

For example:

#define plus3(x) x+3

This replaces the code “plus3(<exp>)” with the code “<exp> + 3“

Careful construction must be used:

y = plus3(x)*5; will result in:

y = x+3*5;

This is not exactly what we intended!

But:

#define plus3(x) (x+3)

will do the job.

Another example:

#define max(n,m) (n<m)?m:n

Will cause problems if n is the expression ++x, it will be incremented twice!

max(++x,y);

will result in:

(++x<y)? ++x : y;

#define max(n,m) { int x = n,y=m; (x<y)?y:x; }

Careful programming must be used with macros. As a general rule, use only expressions without side-effects in C macros or use lexically hygienic macros.

** Assuming x and y are not used elsewhere (the call max(5,x) will fail in this case).

The problem can be resolved** this way:

Daniel
Typewritten text
Macro Expansion in C:
Daniel
Typewritten text
Daniel
Typewritten text
Compiler Principles, PS4: Macro Expansion
Page 2: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

Mit-define -> Define

1. (define (foo x1 x2 … xn)

2. body)

3. =>

4. (define foo

5. (lambda (x1 x2 … xn)

6. body))

And -> If

1. (and e1 e2 e3)

2. =>

3. (if e1 (if e2 e3 #f) #f)

Macro expansion is a syntactical transformation of an expression into an equivalent expression (syntactic sugar). For instance, Let expressions in Scheme are actually macros:

(let ((<Var-1> <Head-1>) … (<Var-k> <Head-k>)) <expr-1> … <expr-m>)

<=>

((lambda (Var-1 … Var-k) <expr-1> … <expr-m>) <Head-1> …<Head-k>)

Daniel
Typewritten text
Macro Expansion in Scheme:
Page 3: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:
Daniel
Typewritten text
Quasi-quoting
Daniel
Typewritten text
Quasi-quote is one of the list construction mechanisms in scheme. It is more flexible than using quote, list or cons as it allows mixing quoting and evaluating in the same context. The quasi-quote synyax (`) is similar to the quote syntax('), with the addition of 2 helpers: unquote (,) and unquote-splicing (,@). When initiating a quasi-quote context, a quoting context is initiated. Unquote allows up to momentarily escape the quoted context and evaluate an expression (instead of quoting an expression). Unquote-splicing behaves like unquote with one additional feature - it "unwraps" the result of the expression.
Daniel
Typewritten text
You can think of the combination of quasi-quote and unquote+splicing as a mechanism for templating+substitution, like printf() in C: printf("this is %s no. %d", "string", 1); is similar to (let ((s 'list) (d 1)) `(this is ,s no. ,d))
Daniel
Typewritten text
A more complex example: (define a 1) (define b 2) (define c '(x y z)) (define d '(u v w)) `(q r ,a ,b s t ,@c ,d) => '(q r 1 2 s t x y z (u v w)) Quasi-quote and the unquote mechanisms can be expanded using list, cons and append. For example: `(q r ,a ,b s t ,@c ,d) => (append (append (list 'q 'r a b 's 't) c) (list d)) Note that the unquoting works on all valid expressions, not only variables: `(q r ,((lambda () 1)) ...) => '(q r 1 ...)
Page 4: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

(or <exp_1> … <exp_k>)

Wrong 0:

(if <exp_1> #t [macro-expand (or <exp_2> … <exp_k>)])

Problem: if <exp_1> is true, the value returned must be <exp_1> and not #t. remember anything that is not #f is true.

Wrong 1:

(if <exp_1> <exp_1> [macro-expand (or <exp_2> … <exp_k>)])

Problem: <exp_1> is evaluated twice!

Wrong 2:

(let ((v <exp_1>)) (if v

v [macro-expand (or <exp_2> … <exp_k>)] )

Problem: If any <exp_i> contains a variable called v – then that variable scope is changed. For example:

(or #f (and v w) #f #f) => (let ((v #f))

(if v v

(let ((v (and v w))) ;; here is the error. This v should refer to v (if v

v ….))

Variable Hygiene

Variable hygiene can be crucial in more complicated expansions. Suppose we want to expand or/and expressions using if:

Page 5: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

Correct:

( (lambda (val_1 rest) (if val_1 val_1 (rest)))

<exp_1> (lambda () [macro-expand (or <exp_2> … <exp_k>)]))

Now the variable val_1 does not override the scope of the original expression. This transformation is Hygienic! Note that although this is correct for the general case, there are special end cases. All end-cases must be treated.

Macro-expansion can be applied on a variety of built in scheme expressions. Using if and lambda, it is possible to expand let, let*, begin, and, or, and more. Using set! it is possible to also expand letrec as well. Note that the quote mechanism can in itself be macro-expanded using the standard list and pair operators.

Wrong 3:

Use gensym.

Why is it wrong? because gensym is a sloppy and ugly mechanism. If you can avoid it, do so.

Page 6: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

Expanding the Language

Important advantage of macro-expansion - lets programmers expand the language using their own defined expressions.

For instance, we can add a FOR statement to the scheme language by simply adding macro- expansion to the front end of the compiler:

(for <index_var> <from-expr> <to-expr> <body-expr>)

is defined as follows:

1. Evaluate <from-expr> and <to-expr> - to initial and final values (respectively).

2. Set <index_var> to initial value

3. If <index_var> is greater than final value, return `done.

4. Evaluate <body-expr>

5. Increment <index_var> by 1.

Basic principles:

* Correctness * Hygiene (no gensym)

Code after expansion:

(let ((from <from-expr>)) (to <to-expr>) (body-thunk (lambda () <body-expr>))

(letrec ((<index-var> from) (loop

(lambda () (if (>= to <index-var>)

(begin (body-thunk) (set! <index-var> (add1 <index-var>)) (loop))

‘done)))) (loop)))

Page 7: #define plus3(x) x+3 - BGUcomp141/wiki.files/ps4.pdf · 2013-11-08 · The C preprocessor can be used to define macros in C, macros are defined using a #define directive. For example:

Exercise:

Expand if using only and, or and lambda.

(if <test> <then> <else>)

(let ((val-test <test>)) (or (and val-test <then>)

(and (not val-test) <else>)

Not good enough: No variable hygiene (val-test).

Correct:

(let ((then-thunk (lambda () <then>)) (else-thunk (lambda () <else>))) (val-test <test>))

(or (and val-test (then-thunk)) (and (not val-test) (else-thunk))))

Also correct:

(let ((then-thunk (lambda () <then>)) (else-thunk (lambda () <else>))) (val-test <test>))

((or (and val-test then-thunk)

else-thunk)))

Wrong: