7.0.0.6

4 Lab ... and Yet More Syntax

Goals

more practice with syntax-parse

practice with define-syntax-class

practice with syntax-parse directives

Exercise 6. Develop the macro where, which is a kind of backward let that has an expression followed by a binding that can be used by the expression. The binding is evaluated before the expression (even though it appears later). For example,
(where (+ my-favorite-number 2)
  [my-favorite-number 8])
produces 10.

After you have where with a single binding working, generalize it to support multiple bindings. With that generalization,
(where (op 10 (+ my-favorite-number an-ok-number))
  [my-favorite-number 8]
  [an-ok-number 2]
  [op *])
produces 100.

Now implement where*, which is a backwards let* that evaluates its clauses and binds in reverse order:
(where* (list x y z)
  [x (+ y 4)]
  [y (+ z 2)]
  [z 1])
produces the same values as (list 7 3 1). image

Exercise 7. Develop the macro and/v, which is like and except that it binds the result of its first expression to a variable written => identifier. For example,

(and/v 1 => x (+ x 1))

evaluates to 2, but

(and/v #f => x (+ x 1))

evaluates to #f. You can use #:literals in syntax-parse, since => is already bound (assuming you think that binding’s reuse is appropriate).

After getting and/v to work, make the => identifier part optional. Note that both => and identifier must be both omitted or both present. image

Exercise 8. Modify the definition of split-ct from lecture so that it can also deal with specifications that do not provide literal constants for start and end. When a non-literal start or end is provided, the rage check should happen at run time.

Here is the code from lecture:
#lang racket
 
(require (for-syntax syntax/parse))
 
(begin-for-syntax
  (define-syntax-class byte
    (pattern b:nat #:fail-unless (< (syntax-e #'b) 256) "not a byte")))
 
; SYNTAX
; (split-ct tags start end [name:id step (~optional convert)] ...)
; computes the values of the fields name... by successively extracting
; bytes from tags, beginning at start to maximally end
(define-syntax (split-ct stx)
  (syntax-parse stx
    [(_ tags start:integer end:byte [name step:byte (~optional convert)] ...)
     ; 
     ; the static error checking
     #:do [(define end-int  (syntax-e #'end))
           (define step-int (sum #'(step ...)))]
     #:fail-unless (< step-int end-int) "index out of range"
     ; 
     #`(let ([i start])
         (let*-values ([(i name) (values (+ i step) (extract tags i (+ i step -1)))]
                       ...)
           (values ((~? convert values) name) ...)))]))
 
; [Listof [Syntax Number]] -> Number
; compute the sum of the numbers hidden in syntax
(define-for-syntax (sum list-of-syntax-numbers)
  (apply + (map syntax-e (syntax->list list-of-syntax-numbers))))
Start by copying and pasting this code into your DrRacket. image

Exercise 9. Start with the code from lecture and add the following pre-defined, arity-checked functions:
  • plus, which takes two number arguments and adds them;

  • minus, which takes two number arguments and adds them; and

  • string+, which takes two srting arguments and concatenates them.

Add these without using define-function.

For example, (function-app plus 1 2) should produce 3, but your implementation should not include (define-function (plus x y) ***).

These primitives must work with the protocol from lecture. If you fancy adding other pre-defined function, please feel free to do so. image

Exercise 10. Add ++ as a new form to the algebra language:
  Expression = ....
  | (++ Expression)
A (++ Expression) form produces a number 1 larger than the value of Expression. image

Exercise 11. Develop a macro that consumes an identifier x and generates a definition for x that initializes it to how many such definitions (including the one for x) have been created so far in the whole program. For example, if your macro is called define-as-next, then
(define-as-next x)
(define-as-next y)
(define-as-next z)
defines x as 1, y as 2, and z as 3.

When you have a solution, try this:

(define-as-next x)
(define (get-y)
  (define-as-next y)
  y)
(define one-y (get-y))
(define another-y (get-y))
What values do x, one-y, and another-y get? Explain. Try adding (define-as-next z) to the end again and check the value of z.

Next, encapsulate the definition of the compile time function in a module named server and the uses of the function in a module called client. Your Definitions window will look like this:
#lang racket
 
(module server racket
  (provide define-as-next)
  ***
  (define-syntax (define-as-next stx) ***))
 
(module client racket
  (require (submod ".." server))
  ***)
Does your solution still work? If not, fix it.

Now duplicate client, use client2 for the second one. Run your program. What do you see? Explain. image

Please fill out today’s post-day survey.