【发布时间】:2021-01-15 09:35:45
【问题描述】:
我正在尝试制作一个使用宏 (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 宏的实现有什么问题?如何让宏评估它的一些参数?
【问题讨论】:
-
您已经展示了对
doc宏的两个示例调用,但是您还没有展示它们的预期扩展。仅显示代码迫使我们同时阅读它以理解预期的扩展并搜索错误。您可以通过描述和显示预期结果使我们更容易,而对于宏,预期结果是代码扩展。 -
@WillNess 好的,谢谢你告诉我。我已经编辑了问题(编辑#5)。你能检查我是否包含了必要的信息吗?
-
谢谢。 (a)
(doc id: 1 "First sentence." "Second sentence.")是有效的宏调用吗? (b)(doc id: 1 "First sentence." "Second sentence." title: "First document")是否有效? (c)(doc id: 1 "First sentence." "Second sentence." title: "First document" subtitle: "First subdocument" "Third sentence." )? (d)(doc id: 1 title: "First document" subtitle: "First subdocument" "First sentence." "Second sentence." "Third sentence." )? -
@WillNess (a) 和 (d) 有效,而 (b) 和 (c) 无效。元数据必须在句子之前。宏必须接受任意数量的元数据。
-
顺便说一句,我认为从名称中的冒号检测元数据会很困难。因为无论如何都必须先进行,所以您可以轻松更改格式以将所有元数据条目放在 list 中。那么这个宏就变得更容易编写了。然后 (d) 变为
(doc (id: 1 title: "First document" subtitle: "First subdocument") "First sentence." "Second sentence." "Third sentence." )。这样的改变可以接受吗?