语法对象是底层 Racket 编译器的词法上下文的存储库。具体来说,当我们进入如下程序时:
#lang racket/base
(* 3 4)
编译器接收代表该程序全部内容的语法对象。这是一个示例,让我们看看该语法对象的样子:
#lang racket/base
(define example-program
(open-input-string
"
#lang racket/base
(* 3 4)
"))
(read-accept-reader #t)
(define thingy (read-syntax 'the-test-program example-program))
(print thingy) (newline)
(syntax? thingy)
请注意,*
程序中的 具有编译时表示,作为 中的语法对象thingy
。目前,*
inthingy
不知道它来自哪里:它还没有绑定信息。正是在扩展过程中,在编译过程中,编译器将其关联*
为对*
of的引用#lang racket/base
。
如果我们在编译时与事物交互,我们可以更容易地看到这一点。(注意:我故意避免谈论,eval
因为我想避免混淆在编译时和运行时发生的事情的讨论。)
这是一个示例,让我们更多地检查这些语法对象的作用:
#lang racket/base
(require (for-syntax racket/base))
;; This macro is only meant to let us see what the compiler is dealing with
;; at compile time.
(define-syntax (at-compile-time stx)
(syntax-case stx ()
[(_ expr)
(let ()
(define the-expr #'expr)
(printf "I see the expression is: ~s\n" the-expr)
;; Ultimately, as a macro, we must return back a rewrite of
;; the input. Let's just return the expr:
the-expr)]))
(at-compile-time (* 3 4))
我们将在这里使用一个宏at-compile-time
,来让我们在编译期间检查事物的状态。如果你在 DrRacket 中运行这个程序,你会看到 DrRacket 首先编译程序,然后运行它。在编译程序时,当它看到使用 of 时at-compile-time
,编译器将调用我们的宏。
所以在编译时,我们会看到类似:
I see the expression is: #<syntax:20:17 (* 3 4)>
让我们稍微修改一下程序,看看我们是否可以检查identifier-binding
标识符:
#lang racket/base
(require (for-syntax racket/base))
(define-syntax (at-compile-time stx)
(syntax-case stx ()
[(_ expr)
(let ()
(define the-expr #'expr)
(printf "I see the expression is: ~s\n" the-expr)
(when (identifier? the-expr)
(printf "The identifier binding is: ~s\n" (identifier-binding the-expr)))
the-expr)]))
((at-compile-time *) 3 4)
(let ([* +])
((at-compile-time *) 3 4))
如果我们在 DrRacket 中运行这个程序,我们将看到以下输出:
I see the expression is: #<syntax:21:18 *>
The identifier binding is: (#<module-path-index> * #<module-path-index> * 0 0 0)
I see the expression is: #<syntax:24:20 *>
The identifier binding is: lexical
12
7
(顺便说一句:为什么我们会从at-compile-time
前面看到输出?因为编译完全是在运行时之前完成的!如果我们预编译程序并使用raco make保存字节码,我们将不会在运行时看到编译器被调用该程序。)
当编译器使用 时at-compile-time
,它知道将适当的词法绑定信息与标识符相关联。当我们identifier-binding
在第一种情况下检查时,编译器知道它与特定模块相关联(在这种情况下,#lang racket/base
,这就是该module-path-index
业务所涉及的)。但是在第二种情况下,它知道它是一个词法绑定:编译器已经遍历了(let ([* +]) ...)
,因此它知道使用*
引用回到由let
.
Racket 编译器使用语法对象将这种绑定信息传递给客户端,例如我们的宏。
尝试用它eval
来检查这类东西充满了问题:语法对象中的绑定信息可能不相关,因为当我们评估语法对象时,它们的绑定可能会引用不存在的东西!这就是您在实验中看到错误的根本原因。
不过,这里有一个例子展示了 s 表达式和语法对象之间的区别:
#lang racket/base
(module mod1 racket/base
(provide x)
(define x #'(* 3 4)))
(module mod2 racket/base
(define * +) ;; Override!
(provide x)
(define x #'(* 3 4)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;
(require (prefix-in m1: (submod "." mod1))
(prefix-in m2: (submod "." mod2)))
(displayln m1:x)
(displayln (syntax->datum m1:x))
(eval m1:x)
(displayln m2:x)
(displayln (syntax->datum m2:x))
(eval m2:x)
这个例子是精心构造的,因此语法对象的内容只涉及到模块绑定的东西,这些东西在我们使用的时候就存在eval
。如果我们稍微改变一下这个例子,
(module broken-mod2 racket/base
(provide x)
(define x
(let ([* +])
#'(* 3 4))))
然后当我们尝试eval
从. 是一只困难的野兽。x
broken-mod2
eval
eval