首页 > 解决方案 > 如何让宏评估它的一些参数?

问题描述

我正在尝试制作一个使用宏 ( doc) 编写文档的玩具系统:

示例 #1:

(doc id: 1
     title: "First document"
     "First sentence."
     "Second sentence.")

预期扩展:

(make-doc (list (list 'id: 1) (list 'title: "First document"))
          (list "First sentence" "Second sentence"))

示例 #2:

(let ((my-name "XYZ"))
 (doc title: "Second document"
      id: (+ 1 1)
      "First sentence."
      (string-append "My name is " my-name ".")
      "Last sentence."))

预期扩展:

(let ((my-name "XYZ"))
  (make-doc (list (list 'title: "Second document") (list 'id: (+ 1 1)))
            (list "First sentence."
                  (string-append "My name is " my-name ".")
                  "Last sentence.")))

对该宏的更多示例调用是:

(doc id: 1 "First sentence." "Second sentence.")

(doc id: 1 title: "First document" subtitle: "First subdocument" 
     "First sentence." "Second sentence." "Third sentence.")

首先是元数据规范,然后是句子。元数据必须在句子之前。宏必须接受任意数量的元数据规范。

评估(doc ...)应该返回一个字符串,或者将结果文本写入文件。但是我还没有实现这个功能,因为我被困在doc宏的定义上(这是这个问题的重点)。

下面是我对doc宏的实现。词汇:title: "ABC"并被id: 123称为“元数据”;title:并被id:称为“元数据 ID”。

;;; (metadata-id? 'x:) -> #t
;;; (metadata-id? 'x) -> #f
;;; (metadata-id? "Hi!") -> #f
(define (metadata-id? x)
  (cond [(symbol? x)
         (let* ([str (symbol->string x)]
                [last-char (string-ref str (- (string-length str) 1))])
           (char=? last-char #\:))]
        [else #f]))

;;; (pair-elements '(1 2 3 4 5)) -> '((1 2) (3 4) (5)).
(define (pair-elements l [acc '()] [temp null])
  (cond [(and (null? l) (null? temp)) acc]
        [(null? l)
         (append acc (list (list temp)))]
        [(null? temp)
         (pair-elements (cdr l) acc (car l))]
        [else
         (pair-elements (cdr l)
                        (append acc (list (list temp (car l)))))]))

(define-syntax doc
  (syntax-rules ()
    ((doc arg . args)
     (let* ([orig-args (cons 'arg 'args)]
            [metadata-bindings (takef (pair-elements orig-args)
                                      (lambda (e)
                                        (metadata-id? (car e))))]
            [sentences (drop orig-args (* 2 (length metadata-bindings)))])
       (make-doc metadata-bindings sentences)))))

(define (make-doc metadata-bindings sentences)
  ;; Do something ...
  ;; Placeholder stubs:
  (writeln metadata-bindings)
  (writeln sentences))

使用此实现,评估示例 #1 会按预期打印:

((id: 1) (title: "First document"))
("First sentence." "Second sentence.")

但是,评估示例 #2 打印:

((id: (+ 1 1)) (title: "Second document"))
("First sentence." (string-append "My name is " my-name ".") "Last sentence.")

显然,这些论点没有被评估。示例 #2 的预期结果应该是这样的:

((id: 2) (title: "Second document"))
("First sentence." "My name is XYZ." "Last sentence.")

doc宏的实现有什么问题?我怎样才能让宏评估它的一些参数?

标签: macrosschemeracket

解决方案


原因是您正在引用'args,这导致它在宏扩展后成为 s 表达式,而不是使用函数应用程序进行评估。要解决此问题,您可能需要使用 quasiquote。这还需要您重新设计宏模式的指定方式。我建议使用...符号。这是我所描述的草图:

(define-syntax doc
  (syntax-rules ()
    [(doc arg rest-args ...)
     (let* ([orig-args `(arg ,rest-args ...)]
            ; the rest is the same
            ))])) 

推荐阅读