Racket 试图理解语言扩展中的需求

Racket 试图理解语言扩展中的需求,racket,Racket,我试图在racket中定义一种新的语言,我们称之为wibble。Wibble将允许加载模块,因此它必须将其表单转换为Racket-require表单。但是当我在语言扩展中使用时,我很难获得工作要求。我最终把我的问题归结为以下奇怪的行为 这是我的阅读器,它重新定义了读取和读取语法 === wibble/lang/reader.rkt === #lang racket/base (provide (rename-out (wibble-read read) (wibble-read-syntax

我试图在racket中定义一种新的语言,我们称之为wibble。Wibble将允许加载模块,因此它必须将其表单转换为Racket-require表单。但是当我在语言扩展中使用时,我很难获得工作要求。我最终把我的问题归结为以下奇怪的行为

这是我的阅读器,它重新定义了
读取
读取语法

=== wibble/lang/reader.rkt ===
#lang racket/base

(provide (rename-out (wibble-read read) (wibble-read-syntax read-syntax)))

(define (wibble-read in)
  (wibble-read-syntax #f in))

(define (wibble-read-syntax src in)
  #`(module #,(module-name src) wibble/lang
      #,@(read-all src in)))

(define (module-name src)
  (if (path? src)
      (let-values (((base name dir?) (split-path src)))
        (string->symbol (path->string (path-replace-suffix name #""))))
      'anonymous-module))

(define (read-all src in)
  (let loop ((all '()))
    (let ((obj (read-syntax src in)))
      (if (eof-object? obj)
          (reverse all)
          (loop (cons obj all))))))
这是我简化的语言模块,它将
(require racket/base)
引入到每个wibble模块中

=== wibble/lang.rkt ===
#lang racket/base

(require (for-syntax racket/base))

(provide (rename-out (wibble-module-begin #%module-begin)) #%app #%datum #%top)

(define-syntax wibble-module-begin
  (lambda (stx)
    (syntax-case stx ()
      ((_ x ...) #`(#%module-begin (require #,(datum->syntax stx 'racket/base)) x ...)))))
使用上述代码,则此wibble代码“有效”,即没有错误

#lang wibble
(cons 1 2)
(cons 3 4)
但是下面

#lang wibble
(cons 1 2)
在:cons中的模块中给出错误消息
cons:unbound标识符

真的,我只是想解释一下到底发生了什么。我确信与racket文档(racket参考3.1)的差异与此相关

如果提供了单个表单,那么它将在 模块开始上下文。如果扩展导致#%plain模块开始, 那么#%plain模块begin的主体就是模块的主体。 如果部分展开导致任何其他基元形式,则该形式 用#%模块包装,开始使用模块的词法上下文 身体;此标识符必须由初始模块路径导入绑定, 它的扩展必须产生一个#%的普通模块开始提供 模块体。最后,如果提供了多个表单,则将它们包装起来 以#%模块开始,如在单个窗体不开始的情况下 展开到#%plain模块开始

但即便如此,我也不明白为什么只有一种形式会有任何不同,这似乎与部分扩张的时机有关,但我不太确定。我也不明白为什么Racket把一个表单当作一个特例

顺便说一句,我可以通过稍微修改一下我的阅读器来解决这个问题

(define (wibble-read-syntax src in)
  #`(module #,(module-name src) wibble/lang
      #,@(read-all src in) (void)))
硬编码一个
(void)
表单意味着我总是有多个表单,而且所有的东西都可以工作

抱歉发了这么长的帖子,我只是想了解一下这些东西是如何工作的。

好吧,我想我已经明白了

您的直觉是正确的,因为问题在于单表单模块体部分扩展的时间。在
reader.rkt
文件中,生成一个
(模块…
表单。正如您问题的摘录所述,
forms…
部分将被特别处理,因为只有一个。让我们来看一个关于部分扩展文档的摘录:

作为一种特殊情况,当扩展将向表达式添加
#%app
#%datum
#%top
标识符时,当绑定原来是原语
#%app
#%datum
#%top
表单时,扩展停止,而不添加标识符

我几乎可以肯定,此时发生的部分扩展对
cons
标识符有影响。这是我仍然不确定的一部分。。。我的直觉告诉我,发生的事情是,部分扩展试图找到
cons
标识符的绑定(因为它是括号的第一部分,标识符可以绑定到一个应该扩展的宏,因此需要检查),但无法找到,所以它抛出了一个问题。请注意,即使
cons
没有阶段1(语法扩展时间)绑定,宏扩展器仍然希望标识符有阶段0(运行时)绑定(除其他外,这有助于扩展器保持卫生)。因为所有这些部分扩展都发生在
(module…
表单的主体上(在
(#%module begin…
表单插入
(#%require…
表单之前完成),
cons
在扩展过程中没有绑定,所以我相信扩展失败了

然而,解决问题的一个简单方法是重写
wibble-read语法,如下所示:

(定义(wibble读取语法src-in)
(让*((读入(读入所有src))
(在stx中(和(成对读入)(汽车读入)))
#`(模块#,,(模块名src)wibble/lang
(需要#,(基准->stx球拍/球座中的语法)
#,@读入)
然后可以从
(#%module begin…
宏中删除
(#%require…
表单

然而,在我看来,这并不是解决这个问题的最佳方法。出于清洁的考虑,在
require
格式中进行硬编码,就像您在
wibble/lang.rkt
中所做的那样。一种更简单的方法是将
lang.rkt
文件更新为如下内容:

==wibble/lang.rkt===
#球拍/球座
(需要(用于语法球拍/底座))
(提供(重命名(wibble模块开始#%module开始))
(除外(全部从外球拍/球座)#%模块开始#%应用程序#%基准面#%顶部)
#%应用程序#%datum#%top)
(定义语法wibble模块begin
(lambda(stx)
(语法大小写stx()
((x…)`(#%模块开始x…)))
在本约定中写入,不需要任何硬编码的
(require…)
形成并防止像您发现的那样的细微错误发生。如果您不明白为什么这样做,请记住,您已经使用此文件提供了
\%模块begin
标识符,该文件随后会绑定到所有
\\ lang wibble
文件中。原则上,您可以存储的标识符没有限制如果你想进一步阅读的话,这是一个无耻的自我广告,让我们回到这个话题上来


我希望我能帮上忙。

问题在于
要求
(尽管我不确定自己是否100%理解所有行为)

(需要X)
导入bindi
(define-syntax wibble-module-begin
  (lambda (stx)
    (syntax-case stx ()
      [(_) #'(#%module-begin)]
      [(m x y ...)
       #`(#%module-begin
          (require #,(datum->syntax #'x 'racket/base))
          x y ...)])))