6

我正在尝试在 python 中编写一种类似于方案的小型语言,以便更好地理解方案。

问题是我被困在语法对象上。我无法实施它们,因为我并不真正了解它们的用途以及它们的工作方式。

为了理解它们,我在 DrRacket 中使用了一些语法对象。

从我能够找到的情况来看,评估#'(+ 2 3)与评估没有什么不同'(+ 2 3),除非在+顶级命名空间中有一个词法变量遮蔽了该变量,在这种情况下(eval '(+ 2 3))仍然返回5,但(eval #'(+ 2 3))只是抛出一个错误。

例如:

(define (top-sym)
  '(+ 2 3))
(define (top-stx)
  #'(+ 2 3))
(define (shadow-sym)
  (define + *)
  '(+ 2 3))
(define (shadow-stx)
  (define + *)
  #'(+ 2 3))

(eval (top-sym)), (eval (top-stx)), and (eval (shadow-sym))all return 5, while(eval (shadow-stx))抛出错误。他们都没有回来6

如果我不知道更好,我会认为语法对象的唯一特别之处(除了它们存储代码位置以便更好地报告错误的微不足道的事实)是它们在某些情况下抛出错误符号对应项将返回一个可能不需要的值。

如果故事就这么简单,那么使用语法对象相对于常规列表和符号将没有真正的优势。

所以我的问题是:我错过了什么让它们如此特别的语法对象?

4

1 回答 1

12

语法对象是底层 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从. 是一只困难的野兽。xbroken-mod2evaleval

于 2013-03-16T22:35:59.727 回答