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
____________________
Racket Users list:
http://lists.racket-lang.org/users