[racket] Defining Syntax in Racket

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Sun Apr 20 16:56:32 EDT 2014

The short answer is this:

   A macro should NEVER call syntax->datum on an expression
   or a term that contains expressions.

The syntax->datum call throws away important information, and you're 
seeing the consequences when you split your code into separate modules.

Here's how I would rewrite the macro:

;; (%rule (var-id ...) clause ...)
;; where clause = [(expr ...) subgoal ...]
;;       subgoal = (subgoal-fun-expr arg-expr ...)
;; Example:
;;    (%rule () [() (%noun-phrase) (%verb-phrase)])
;; => (%rel (s0 s1 s2)
;;      [(s0 s2) (%noun-phrase s0 s1) (%verb-phrase s1 s2)])
(define-syntax (%rule stx)
   (syntax-case stx ()
     [(_ (v ...) clause ...)
      (let ()
        ;; How many aux vars might we need? Each subgoal connects
        ;; two vars, so each clause needs one more than number of
        ;; subgoals, so need max over all clauses.
        ;; FIXME: what if no claues?
        (define aux-var-count
          (apply max (for/list ([clause (syntax->list #'(clause ...))])
                       (add1 (clause-count-subgoals clause)))))
        (define all-aux-vars
          (generate-temporaries (make-list aux-var-count #'s)))
        (with-syntax
            ([(aux-var ...) all-aux-vars]
             [(new-clause ...)
              (for/list ([clause (in-list (syntax->list #'(clause ...)))])
                (rewrite-clause clause all-aux-vars))])
          #'(%rel (aux-var ... v ...) new-clause ...)))]))

;; Compile-time helper functions
(begin-for-syntax
   ;; clause-count-subgoals : Syntax -> Nat
   (define (clause-count-subgoals clause)
     (syntax-case clause ()
       [((a ...) subgoal ...)
        (length (syntax->list #'(subgoal ...)))]))

   ;; rewrite-clause : Syntax (Listof Identifier) -> Syntax
   ;;    ((a ...) (subgoalfn_0 e ...) ... (subgoalfn_n e ...))
   ;; => ((a ... aux_0 aux_n+1)
   ;;     (subgoalfn_0 e ... aux_0 aux_1) ...
   ;;     (subgoalfn_n e ... aux_n aux_n+1))
   (define (rewrite-clause clause all-aux-vars)
     (define subgoal-count (clause-count-subgoals clause))
     (define aux-vars (take all-aux-vars (add1 subgoal-count)))
     (syntax-case clause ()
       [((a ...) subgoal ...)
        (with-syntax
            ([(new-subgoal ...)
              (for/list ([subgoal (in-list (syntax->list #'(subgoal ...)))]
                         [start-aux (in-list (drop-right aux-vars 1))]
                         [end-aux   (in-list (drop aux-vars 1))])
                (rewrite-subgoal subgoal start-aux end-aux))]
             [start-aux (first aux-vars)]
             [end-aux   (last aux-vars)])
          #'((a ... start-aux end-aux) new-subgoal ...))]))

   ;; rewrite-subgoal : Syntax Identifier Identifier -> Syntax
   ;; (subgoalfn_k e ...) => (subgoalfn_k var_k var_k+1 e ...)
   (define (rewrite-subgoal subgoal start-aux end-aux)
     (syntax-case subgoal ()
       [(subgoalfn e ...)
        (with-syntax ([start-aux start-aux]
                      [end-aux end-aux])
          #'(subgoalfn e ... start-aux end-aux))]))
   )


I changed a few things that weren't strictly related to the 
syntax->datum bug. For example, I used generate-temporaries instead of 
creating symbols with predictable names.

The rewrite uncovered another bug: the original code added the extra 
arguments to the front of the pattern list but the end of the subgoal 
applications.

Ryan


On 04/20/2014 10:26 AM, Scott Brown wrote:
> I am trying to write a Definite Clause Grammar syntax for Racklog, analogous to Prolog's DCG extension. Basically, I have created a syntax definition %rule so that:
>
> (define %sentence (%rule () [() (%noun-phrase) (%verb-phrase)]))
>
> would be redefined as:
>
> (define %sentence (%rel (s0 s1 s) [(s0 s) (%noun-phrase s0 s1) (%verb-phrase s1 s)]))
>
> This code that I have so far works:
>
> #lang racket
> (require racklog (for-syntax racket))
>
> ;;; Define a rule using DCG notation.
> (define-syntax (%rule stx)
>    (syntax-case stx ()
>      [(_ (v ...) ((a ...) subgoal ...) ...)
>       (with-syntax ([gls (for/list ([g (syntax->list #'(((a ...) subgoal ...) ...))]) (length (syntax->list g)))])
>         (with-syntax ([rule (append '(%rel)
>                                     (list (append (for/list ([i (in-range (sub1 (apply max (syntax->datum #'gls))))])
>                                                     (string->symbol (~a "s" i))) '(s) #'(v ...)))
>                                     (for/list ([g (syntax->datum #'(((a ...) subgoal ...) ...))])
>                                       (cons (append '(s0 s) (first g))
>                                             (for/list ([sg (rest g)]
>                                                        [i (in-range (length (rest g)))])
>                                               (append sg (list (string->symbol (~a "s" i))
>                                                                (if (= i (sub1 (length (rest g)))) 's
>                                                                    (string->symbol (~a "s" (add1 i))))))))))])
>           #'rule))]))
>
> ;;; Define a terminal using DCG notation.
> (define-syntax (%term stx)
>    (syntax-case stx ()
>      [(%term (t ...) ...) #'(%rel (x) [((append '(t ...) x) x)] ...)]))
>
> ;;; A simple English grammar in DCG notation.
> (define %sentence (%rule () [() (%noun-phrase) (%verb-phrase)]))
> (define %noun-phrase (%rule () [() (%proper-noun)]
>                                 [() (%det) (%noun)]
>                                 [() (%det) (%noun) (%rel-clause)]))
> (define %verb-phrase (%rule () [() (%trans-verb) (%noun-phrase)]
>                                 [() (%intrans-verb)]))
> (define %rel-clause (%rule () [() (%dem-pronoun) (%verb-phrase)]))
> (define %det (%term [the] [every] [a]))
> (define %noun (%term [cat] [bat]))
> (define %proper-noun (%term [john] [mary]))
> (define %dem-pronoun (%term [that]))
> (define %trans-verb (%term [eats]))
> (define %intrans-verb (%term [lives]))
>
> ;;; Tests
> (%which (x) (%sentence x null))
> (%which () (%sentence '(a cat eats the bat) null))
> (%which (x) (%noun x null))
>
>
> The problem I am having is that while this works when %rule is included in the same module (as above), when I move %rule and %term into a different module, I get the error "%noun-phrase: unbound identifier in module in: %noun-phrase". I suspect that it is some kind of a namespace issue, or possibly a phase issue.
>
> I have tried rewriting %rule a couple of different ways (one using syntax-rules rather than syntax-case, and another using quasiquote/unquote rather than with-syntax). Neither of which worked. Anyhow, I've spent a lot of time trying to figure this out, and I finally am at the point where I need some help.
>
> -Scott
>
>
>
>
> ____________________
>    Racket Users list:
>    http://lists.racket-lang.org/users
>


Posted on the users mailing list.