第三章 调试

Chez Scheme 有几个用于调试的功能。除了对经过完全类型检查过的运行中的代码提供错误信息之外,Chez Scheme 也支持对过程调用进行跟踪,中断任意计算,重定义异常和中断处理器,检查任意对象,包括异常的continuation 和中断。

SchemeChez Scheme 的新手程序员,甚至是有经验的 Scheme 程序员都可以参考“How to Debug Chez Scheme Programs” 这个教程。在 http://www.cs.indiana.edu/chezscheme/debug/ 可以找到 HTML 和 PDF 版本的教程。(译者注:此链接在翻译时无法打开 2019/2/1)

3.1 跟踪

跟踪是调试 Scheme 程序最有用的手段之一。 Chez Scheme 可以跟踪任何内建和用户定义的过程。跟踪库会将每一个调用过程的参数和返回值打印出来,通过紧凑的缩进机制来显示嵌套调用的深度。打印非尾递归调用的时候会增加缩进而尾递归调用不会,可以以此区分是尾递归调用还是非尾递归调用。对于嵌套大于等于 10 的调用,会在缩进处有一个括号,其中包括一个数字用于说明嵌套的深度。

这一节会涵盖跟踪过程和控制跟踪输出的机制。

  • 语法: (trace-lambda name formals body1 body2 …)
  • 返回: 一个被跟踪的过程
  • 所属库: (chezscheme)

一个 trace-lambda 表达式等价于一个 lambda 表达式加上一样的语法形式以及程序体,除了无论何时该过程被调用的时候都会向跟踪端口输出跟踪信息。这里使用 name 来命名该过程。跟踪信息会显示过程的参数以及过程的返回值,嵌套的调用会以缩进显示。

下面被跟踪的过程 half 返回整型参数除以 2 的商。

(define half
  (trace-lambda half (x)
                (cond
                 [(zero? x) 0]
                 [(odd? x) (half (- x 1))]
                 [(even? x) (+ (half (- x 1)) 1)])))

对于 (half 5) 这个返回值是 2 的调用跟踪如下

|(half 5)
|(half 4)
| (half 3)
| (half 2)
| |(half 1)
| |(half 0)
| |0
| 1
|2

以上的例子展示了跟踪库对于尾递归和非尾递归调用的不同处理方式。因为 half 在参数是奇数的时候会调用自身,所以 (half 5)(half 4) 的缩进层级是一样的,另外,因为 (half 5)(half 4)的返回值是一样的所以这两个调用的返回值只显示一个。

  • 语法: (trace-case-lambda name clause ...)
  • 返回值: 一个被跟踪的过程
  • 所属库: (chezscheme)

trace-case-lambda 表达式等价于 case-lambda 加上同样的子句,除了在调用该过程的时候会向跟踪输出端口输出跟踪信息,这里同样使用 name 来标记该过程。跟踪信息会展示此过程对应的参数以及该过程的返回值,同样使用缩进来表示嵌套调用。

  • 语法: (trace-let name ((var expr) …) body1 body2 …)
  • 返回值: 程序体 body1 body2 ... 的值
  • 所属库: (chezscheme)

trace-let 表达式等价与有同样名字、绑定和程序体的 let 表达式除了在进入或重入(通过对绑定在 name 上的过程的调用)trace-let 表达式的时候会向跟踪端口输出跟踪信息。

一个以下形式 trace-let 的表达式

(trace-let name ([var expr] ...)
           body1
           body2
           ...)

可以使用 trace-lambda 重写为以下形式:

((letrec ([name
           (trace-lambda name (var ...)
                         body1  boddy2)])
   name)
 expr ...)

trace-let 可以用来跟踪 let 表达式以及具名的 let 表达式,只要这个名字在 let 表达式的程序体中不是自由变量。有时候也可以将 trace-let 插入到程序中用于展示当前跟踪程序任意表达式的值。 例如下面这个版本的 half

(define half
  (trace-lambda half (x)
                (cond
                  [(zero? x) 0]
                  [(odd? x) (half (trace-let decr-value () (- x 1)))]
                  [(even? x) (+ (half (- x 1)) 1)])))

调用 (half 5) 会有如下输出

|(half 5)
| (decr-value)
| 4
|(half 4)
| (half 3)
| |(decr-value)
| |2
| (half 2)
| |(half 1)
| | (decr-value)
| | 0
| |(half 0)
| 1
|2
  • 语法: (trace-do ((var init update) …) (test result …) expr …)
  • 返回值: 最后一个 result 的值
  • 所属库: (chezscheme)

trace-do 等价与拥有子语句的的 do 表达式,除了向跟踪端口输出跟踪信息。跟踪信息包括 var …的值、每次迭代以及循环结束后的最终返回值。

例如,表达式:

(trace-do ([old '(a b c) (cdr old)]
           [new '() (cons (car old) new)])
          ((null? old) new))

会有如下的跟踪输出

|(do (a b c) ())
|(do (b c) (a))
|(do (c) (b a))
|(do () (c b a))
|(c b a)

并且返回 (c b a)

  • 语法: (trace var1 var2 …)
  • 返回值: 包含 var1 var2 … 的 list
  • 语法: (trace)
  • 返回值: 当前被跟踪的所有顶层变量组成的 list
  • 所属库: (chezscheme)

第一种形式中, trace 会将顶层变量 var1,var2 …(必须是过程),重新赋值为等价的用trace-lambda定义的过程用于展示跟踪信息。

trace 会将被跟踪的过程中的每一个变量原来的值封装。他可以大致如下定义(实际的版本会记录并返回被跟踪变量的值)

(define-syntax trace
  (syntax-rules ()
    [(_ var ...)
     (begin
       (set-top-level-value! 'var
         (let ([p (top-level-value 'var)])
           (trace-lambda var args (apply p args))))
       ...)]))

以这种方式跟踪的过程的跟踪可以通过 untrace (见下文)禁用,将相应变量赋值给不同的未跟踪值,或者随后对同一变量使用跟踪。因为被跟踪的是值而不是绑定,但是一个在跟踪被禁用前已经被跟踪的值,在跟踪被禁止后还是存在的话依然会被跟踪。

trace 不带任何子表达式会返回所有当前被跟踪的变量的 list 。如果一个变量被跟踪并且随后没有被禁用跟踪或者被赋予另一个值那就是当前被跟踪的变量。

下面这段例举了如何在 REPL 中使用 trace

> (define half
    (lambda (x)
      (cond
        [(zero? x) 0]
        [(odd? x) (half (- x 1))]
        [(even? x) (+ (half (- x 1)) 1)])))
> (half 5)
2
> (trace half)
(half)
> (half 5)
|(half 5)
|(half 4)
| (half 3)
| (half 2)
| |(half 1)
| |(half 0)
| |0
| 1
|2
2
> (define traced-half half)
> (untrace half)
(half)
> (half 2)
1
> (traced-half 2)
|(half 2)
|1
1
  • 语法: (untrace var1 var2 …)
  • 语法: (untrace)
  • 返回: 一个未被跟踪的变量的 list
  • 所属库: (chezscheme)

untrace 将 var1、var2 … 这些当前被跟踪的顶层变量恢复为原来的( 被trace 之前的)值,相当于禁止对这些变量的值的跟踪。如果 var1、var2 … 中有未被跟踪的值那么它将会被忽略。如果调用 untrace 的时候不加任何参数,那么所有当前被跟踪的变量都会被复原。

以下这段代码演示了如何在 REPL 中使用traceuntrace 调试一段不正确的过程定义。

> (define square-minus-one
    (lambda (x)
      (- (* x x) 2)))
> (square-minus-one 3)
7
> (trace square-minus-one * -)
(square-minus-one * -)
> (square-minus-one 3)
|(square-minus-one 3)
| (* 3 3)
| 9
|(- 9 2)
|7
7
> (define square-minus-one
    (lambda (x)
      (- (* x x) 1))) ; change the 2 to 1
> (trace)
(- *)
> (square-minus-one 3)
|(* 3 3)
|9
|(- 9 1)
|8
8
> (untrace square-minus-one)
()
> (untrace * -)
(- *)
> (square-minus-one 3)
8

第一次对于 square-minus-one 的调用说明其中有错误,第二次(跟踪后)表明了是在那一步中出现了错误,第三次调用说明已经成功地修复了这个问题,第四次调用说明 untrace 没有消除这次修复。

  • 线程参数: trace-output-port
  • 所属库: (chezscheme)

trace-output-port 这个参数决定了跟踪的信息会被发送给哪个输出端口。无参调用时, trace-output-port 会返回当前跟踪的输出端口。当给定一个参数的时候(必须是文本输出端口), trace-output-port 会改变当前的跟踪输出端口。

  • 线程参数: trace-print
  • 所属库: (chezscheme)

trace-print 的值必须是一个接受两个参数的过程,一个对象以及一个输出端口。跟踪库会使用 trace-print 的值来打印被跟踪的过程的参数和返回值。 trace-print 默认是 pretty-print

跟踪库会在调用 trace-print 之前,为当前的嵌套层级将 pretty-initial-indent 的值设置为一个合适的值 ,以此保证多行输出可以正确地被缩进。

  • 语法: (trace-define var expr)
  • 语法: (trace-define (var . idspec) body1 body2 …)
  • 返回值: 不确定
  • 所属库: (chezscheme)

trace-define 是一个方便的简记用来定义一个变量,然后将其绑定到一个被跟踪的同样名字的过程。 前者等价于

(define var
  (let ([x expr])
    (trace-lambda var args
      (apply x args))))

后者等价于

(define var
  (trace-lambda var idspec
    body1 body2 ...))

对于前者, expr 必须等价于一个过程

> (let ()
    (trace-define plus
      (lambda (x y) 
        (+ x y)))
    (list (plus 3 4) (+ 5 6)))
|(plus 3 4)
|7
(7 11)
  • 语法: (trace-define-syntax keyword expr)
  • 返回值: 不确定
  • 所属库: (chezscheme)

trace-define-syntax 会跟踪 expr 转换器中值的输入和输出,但是会除去展开时用于维护词法作用域的上下文信息。

> (trace-define-syntax let*
    (syntax-rules ()
      [(_ () b1 b2 ...)
       (let () b1 b2 ...)]
      [(_ ((x e) m ...) b1 b2 ...)
       (let ((x e))
         (let* (m ...) b1 b2 ...))]))
> (let* ([x 3] [y (+ x x)]) (list x y))
|(let* (let* [(x 3) (y (+ x x))] [list x y]))
|(let ([x 3]) (let* ([y (+ x x)]) (list x y)))
|(let* (let* [(y (+ x x))] [list x y]))
|(let ([y (+ x x)]) (let* () (list x y)))
|(let* (let* () [list x y]))
|(let () (list x y))
(3 6)

去除了上下文信息之后,展示的语句更加易读但是缺失了精确性,因为同样名字的不同标识符难以区别了。 例如:

> (let ([x 0])
    (trace-define-syntax a
      (syntax-rules ()
        [(_ y) (eq? x y)]))
    (let ([x 1])
      (a x)))
|(a (a x))
|(eq? x x)
#f

3.2 交互式调试器

在一个异常被默认的异常处理器处理之后调用 debug 就可以进入交互式调试器。对于严重或者非警告的状态(异常状态),如果 debug-on-exception 参数是真,也可以直接从默认的异常处理器进入。

在调试器内,命令 “?” 会列出调试命令选项。包括以下用途的命令:

  • 检查 raise continuation
  • 显示状态
  • 检查状态,以及
  • 退出调试器

raise continuation 是被包裹在异常状态内的 continuation,(如果有的话)。标准的异常报告过程、 assertassert-violationerror以及 Chez Scheme 的过程 assertion-violationf, errorfsyntax-error 都会抛出一个对应调用用异常状态包裹的 continuation。这使得程序员可以检查待判定程序出错、冲突、或者失败断言发生所在的帧。

另一种交互式调试器—中断处理器,可以通过键盘中断触发默认的键盘中断处理器或是显式调用过程 break 触发默认的中断处理器来进入。同样地,命令“?”会列出命令选项,包括以下命令:

  • 退出中断处理并继续
  • 重置当前的 café (译注:读取-求值-打印 循环)
  • 终止整个 Scheme 会话
  • 进入新的 café
  • 检查当前的 continuation 并且返回
  • 展示程序的统计信息 (运行时间以及内存使用情况)

通常来说可以通过输入 EOF(文件结束符)来退出调试器中断处理。在 Unix 下是“Control-D”,在 Windows 上是“Control-Z”。

  • 过程: (debug)
  • 返回值: 无返回值
  • 所属库: (chezscheme)

当默认的异常处理接收到严重或者非警告的异常状态,它会展示该状态并且重置当前的 café。在重置之前,它会将状态保存在参数 debug-conditiondebug 过程可被用来检查状态。无论何时内建的错误报告机制抛出一个错误之后,当前错误抛出点的 continuation 都是可以被检查的。更广义地来说, debug 可以检查 make-continuation-condition 创建的任意 continuation状态中包含的 continuation

如果参数 debug-on-exception 被设置为 #t ,对于所有的严重和非警告的状态,默认的异常处理机制会直接进入调试器。命令行参数 --debug-on-exception 可以用来从命令行将 debug-on-exception 设置为 #t,这对于调试脚本或者使用 --script--program 命令行参数运行的顶层程序会非常有用。

3.3 交互式审查器

审查器可以直接通过调用过程 inspect 或者从调试器间接启动。它允许程序员检查循环对象、端口和过程这类没有读取器语法的对象、continuation 对象、程序员无法直接接触到的变量以及通常的可以被打印的Scheme对象。

审查器的首要目的是检查而不是修改对象。但是,可被赋值的变量的值有可能在审查器内被改变。可被赋值的变量通常被限制于赋值在源程序中出现的变量。也可以对于一个对象调用任意的过程(包括一些修改变量的过程例如 set-car! )那些原本就是不可改变的对象是不可以被改变的,例如不可被赋值的变量、过程以及大数(bignum),因为这么做违反了编译器和运行时的假定。

用户在使用时可以看到包括当前对象的打印展示的提示行,如果有必要的话会使用缩写让一行可以放得下。大量的命令可以用来展示当前的对象以及在对象内游走。审查器还提供了可用选项的“在线”描述。命令"?"会显示可以应用于当前对象的可用命令。命令"??" 会显示通用的命令。命令"h"会给出如何使用审查器的简要描述。EOF 字符或者命令"q"可以退出审查器。

  • 过程: (inspect obj)
  • 返回值: 不确定
  • 所属库: (chezscheme)

如上所述,在 obj 上调用审查器。依据当前的对象的类型,不同的审查器可用命令罗列如下:

通用命令

helph 会显示关于如何使用审查器的简要描述

? 展示当前对象对应的可用选项

?? 展示通用参数

printp 打印当前对象(使用 pretty-print

wirtew 写下当前对象(使用 write

size 写下当前对象占用的字节数(通过 compute-size 确定),包括任意当前对象可以访问的对象,除了 同一个审查器会话中已经计算过的。

find expr[g] 会求值 expr , expr 应当是一个接收一个参数的过程, find 命令会搜索当前对象中第一个出现的符合条件的对象(将此对象作为参数给 expr 会返回真)。搜索时将立即值(如:定长数),比 g 更老的值,以及在搜索中已经访问过的点作为叶子结点。如果 g 没有给定,那么会默认设置为当前最大代数,例如 collect-maximum-generation 。如果指定了那么, g 必须是小于等于当前最大代数(译者注:当前对象树的树高),或者符号 static 代表静态生成。如果找到了这个对象那么审查器的焦点会移动到对应的对象上,就像从原本的对象那里走了几步来到了目标对象,因此 up 命令可以确定相对于原本的对象目标对象要怎么找到。

find-next 重复上一个 find,找到下一个符合条件的对象,如果有的话。

upu n 返回上 n 个层级。用于在检查对象中向外移动,n 默认是 1。

topt 返回最外层的检查对象。

forwardf 移动到下 n 个表达式。用于在一个包含一系列元素的对象中从一个元素移动到另一个,例如,list、vector、 record、frame 或 closure。n 默认是 1。

backb 向前移动 n 个表达式。用于在一个包含一系列元素的对象中从一个元素移动到另一个,list、vector、 record、frame 或 closure。n 默认是 1。

=> expr 将当前对象发送给过程 exprexpr 可以在当前行,也可以是接下来几行开始,可以占据多行。

file path 打开对应路径的源代码文件。参数 source-directories (见 12.5 章)决定了源文件的搜索路径

list line count ,罗列从第 line 行开始的 count 行源代码。 line 的默认值是上一次展示的最后一行,count 的默认值是 10 或者上一次展示的代码行数。如果 line 是负数那么, list 会展示上一次展示 line 行之前的代码。

files 展示当前打开的源文件。

markm m 使用符号 m 标记当前位置。如果 m 没有指定,会使用默认唯一标志标记当前位置。

gotog m 回到标记着 m 的位置。如果 m 没有指定,审查器会回到默认标志标记的位置。

new-cafen 进入新的 读取-求值-打印 循环 (所谓的 café),可以得到通常的顶层环境。

quitq 从审查器退出。

resetr 重置当前的 café

aborta x 从 Scheme 退出,状态值设置为 X,默认值是-1。

Continuation 命令

show-framessf 显示接下来的第 n 个帧,如果 n 没有指定,就展示所有的帧。

depth 展示 continuation 中的帧的数量

downd n 移动到 continuation 中的下 n 个帧,n 默认是 1

shows 展示 continuation(下一个帧),如果有的话,展示调用过程的源代码,待判定的调用源码,闭包,帧以及自由变量值。只有在编译对应的 lambda 表达式的时候启用了生成审查器信息时才可以得到源码。

show-localslshows 类似除了不展示自由变量。(如果有自由变量可以通过检查闭包得到)。

lengthl 展示 continuation 中顶层帧的元素的数量。

refr 移动到第 n 个或者命名的帧。 n 默认是 0。如果多个元素有同样的名字,只有一个可以通过名字获得,其他的必须使用数字。

codec 移动到调用过程的源代码。

call 移动到待调用判定的源代码

file 打开包含判定调用的源代码的源文件,如果知道的话。 参数 source-directories (见 12.5 章)决定了使用相对路径时搜索的文件夹的路径。

对于 / 开头的绝对路径(或者 Windows 使用 \ ),审查器会先尝试绝对路径,然后在设定的源代码文件夹下尝试路径的最后一部分(文件名)。对于 ./ (Windows 下是 .\) 或 ../ (Windows 下是 ..\ ) 开头的路径名,审查器首先检查 ... ,然后尝试设定的源代码文件夹中 ... 开头的路径,最后是在源代码文件夹中搜索路径的最后一部分(文件名)。对于其他的(相对的)路径名,审查器会先在设定的源代码目录中寻找整个相对路径,然后是在设定的源代码目录中寻找最后的部分(文件名)。

如果找到了名字相同但是内容与原本的源文件不同的文件,这个文件会被跳过。这通常是因为文件在编译之后又被 修改过。给定一个明确的文件名作为参数来强制打开特定文件(参考上面的通用命令)。

evale expr 在包含帧元素绑定的环境中对 expr 进行求值。在被求值的表达式内部,每一个帧元素 n 可以通过 %n 获取。同样的具名帧可以通过他们的名字得到。只有在编译对应的 lambda 表达式时启用了生成审查器信息时才可以得到名字。

set!!n e 设置第 n 个帧元素为 e ,如果那个元素是可以被赋值的。 n 默认是 0。

Procedure 命令(过程)

shows 展示源代码和过程中的自由变量。只有在编译对应的 lambda 表达式时启用了生成审查器信息时才可以得到源代码。

codec 移动到过程的源代码

file 打开包含源代码的文件,如果知道的话。更多信息参见上文对 continuation 的描述

lengthl 展示记录在过程对象中的自由变量。

refr 移动到第 n 个或者是具名的自由变量。n 的默认值是 0。如果多个自由变量是命名相同那么只有一个可以通过名字获取,剩下的必须通过数字访问。

set!! n e 将第 n 个自由变量的值设置为 e ,前提是该变量可以被赋值。 n 默认是 0。

evale expr 在包含该过程的绑定的环境中对 expr 求值。在被求值的表达式内部,每一个自由变量 n 的值可以通过变量 %n 来获取。具名的变量可以通过他们的名字获取到变量值。只有在编译对应的 lambda 表达式的时候启用了生成审查器信息才可以得到变量名。

Pair(list)命令(列表)

shows n 显示 list 中的第 n 个元素,如果没有指定 n 那么所有元素都会被展示。

lengthl 展示 list 的长度。

car 移动到当前对象的 car 对应的对象。

cdr 移动到当前对象的 cdr 对应的队形。

refr n 移动到当前 list 中的第 n 个元素。 n 默认是 0。

tail n 移动到当前 list 的第 n 个 cdr。 n 默认是 1。

Vector,Bytevector,Fxvector 命令(向量)

shows n ,展示当前 vector 的第 n 个元素,如果 n 没有指定就展示所有的元素。

lengthl 展示向量的长度。

refr n 移动到当前 vector 的第 n 个元素, n 默认是 0。

String 命令(字符串)

shows n 展示当前 string 的第 n 个元素,如果没有指定 n ,就展示所有的元素。

lengthl 展示当前 string 的长度。

refr n 移动到当前 string 的第 n 个元素, n 默认是 0。

unicode n 显示当前 string 的前 n 个元素对应的十六进制 Unicode 标量值。

ascii n 展示 string 的前 n 个对象对应的十六进制 ASCII 编码值,使用 -- 说明对应的字符的 Unicode 编码不在 ASCII 的范围内。

Symbol 命令(符号)

shows 显示 symbol 的数据成员。

valuev 移动到当前 symbol 顶层的值。

namen 移动到symbol的命名。

property-listpl 移动到 symbol 的属性列表。

refr n 移动到 symbol 的第 n 个数据成员。第 0 个数据成员是该 symbol 的顶层值,第 1 个数据成员是 symbol 的名字,第2个数据成员是他的属性列表。n 默认是 0。

Character 命令(字符)

unicode 展示当前字符的十六进制 Unicode 编码值

ascii 展示当前字符的 ASCII 编码值,如果 Unicode 编码值不在 ASCII 的编码范围内则使用 -- 来代替

Box 命令

shows 展示当前 box 的内容 。(译者注:box 像是只有一个元素的向量,通常用来存放最小可修改值)

unboxrefr 移动到被装箱的对象。

Port 命令

shows 展示当前端口的数据成员,包括输入和输出的大小,下标以及缓存数据成员。

name 移动到当前端口的名字。

handler 移动到当前端口的句柄。

output-bufferob 移动到端口的输出缓存。

input-bufferib 移动到端口的输入缓存。

Record 命令 (记录)

shows 显示记录的内容。

fields 移动到记录的数据成员名字列表。

name 移动到记录的名字。

rtd 移动到记录的记录类型描述。

refr name 如果可以访问,移动到记录的具名数据成员。

set!! name value 修改记录对应名字的数据成员的值,如果该成员是可以被修改的。

Transport Link Cell (TLC) 命令

shows 展示 TLC 的数据成员 。

keyval 移动到 TLC 的 keyval。

tonc 移动到 TLC 的 tconc

next 移动到 TLC 的下一个的链接

refr n 移动到符号的第 n 个数据成员。第 0 个数据成员是 keyval,第一个是 tconc,第二个是下一个的链接。 n 默认是 0。

3.4 对象审查器

通过组合不同的审查器的接口,系统还提供了非交互式的审查器。和交互式的审查器一样,也可以使用非交互式的方式来检查那些通常来说无法检查的对象。非交互式审查器遵循单一,面向对象的协议。一般的 Scheme 对象都是封装在过程或者审查器对象中的,这些过程或审查器对象接收一个符号性的信息然后返回关于被封装对象的信息或者是新的封装着对象一部分的审查器对象。

  • 过程: (inspect/object object)
  • 返回: 一个审查器对象过程
  • 所属库: (chezscheme)

inspect/object 用于将一个 Scheme 对象转化为一个审查器对象。所有的审查器对象都接受 typeprintwritesize 这四个消息。 type 用于返回对象的类型的符号表示。 printwrite 一定要和端口参数一起使用。这两条消息用于通过 Scheme 过程 preety-printwrite 将对象的表示写到对应端口。 size 消息会返回当前对象所占用的字节大小的常数表示,包括任意当前对象可以访问的对象,但是除了已经使用同一个inspect/object 调用计算过大小的审查器对象。

除了变量审查器对象所有的审查器对象都接受消息 value ,这会返回当前审查器对象真正封装的对象。

(define x (inspect/object '(1 2 3)))
(x 'type) => pair
(define p (open-output-string))
(x 'write p)
(get-output-string p) => "(1 2 3)"
(x 'length) => (proper 3)
(define y (x 'car))
(y 'type) \rightarrow simple
(y 'value) => 1

Pair(序对)审查器对象 序对审查器对象包含一个 Scheme 的序对。

(pair-object 'type) 返回符号 pair

(pair-object 'car) 返回审查器对象中包含的序对的 car 数据成员。

(pair-object 'cdr) 返回审查器对象中包含的序对的 cdr 数据成员。

(pair-object 'length) 返回语句形式的列表 (与类型有关). 类型部分包含符号 properomproper 或是 circular,这会依据列表结构的不同而不同。计数部分包含该列表中不同的序对的数量。

Box 审查器对象 Box 审查器对象包含 Chez Scheme 的 box。

(box-object 'type) 返回符号 box(box-object 'unbox) 返回包含 box 内容的审查器对象。

TLC 审查器对象 Box 审查器对象包含 Chez Scheme 的 box。

(tlc-object 'type) 返回符号 tlc

(tlc-object 'keyval) 返回一个审查器对象,包含 TLC 的 keyval。

(tlc-object 'tconc) 返回一个审查器对象,包含 TLC 的 tconc。

(tlc-object 'next) 返回一个审查器对象,包含 TLC 的 下一个链接。

Vector, String, Bytevector, 和 Fxvector 审查器对象 .向量(bytevector, string, fxvector)审查器对象包含 Scheme 的向量(bytevectors, strings, fxvectors)。

(vector-object 'type) 返回符号 vector ( string, bytevector, fxvector).

(vector-object 'length) 返回向量或字符串的元素数量。

(vector-object 'ref n) 返回一个审查器对象,包含向量或者字符串的第 n 个元素。

简单审查器对象 ,简单审查器对象包含非结构化,不能修改的对象。包括数字、布尔值、空表、EOF 对象以及 void 对象。 可以直接通过给对象一个 value 参数来检查。

(simple-object 'type) 返回符号 symbol

未绑定审查器对象 ,虽然 Scheme 程序通常无法访问未绑定的对象,但在检查变量时可能会遇到它们。

(unbound-object 'type) 返回符号 unbound

过程审查器对象 过程审查器对象包含一个 Scheme 过程。

(procedure-object 'type) 返回符号 procedure

(procedure-object 'length) 返回自由变量的数量 。

(procedure-object 'ref n) 返回一个审查器对象,包含过程的第 n 个自由变量。详见下面的变量审查器的部分。n必须是非负数且小于过程中自由变量的数量。

(procedure-object 'eval expr)expr 求值并返回其值。过程中的自由变量在被求值的表达式中使用标识符 %n 表示,n 是审查器显示的位置数字。具名的变量同样也绑定到他们本身的名字上。

(procedure-object 'code) 返回一个审查器对象,其中包含过程的代码对象。关于代码审查器对象详见下文。

continuation 审查器对象 通过 call/cc 产生的 continuation 实际上是过程。但是在检查这样一个过程时,有可能会需要将 continuation 的底层数据结构暴露出来才行。一个 continuation 的数据结构包含要恢复计算的位置,执行计算需要用到的变量以及下一个 continuation 的链接。

(continuation-object 'type) 返回符号 continuation

(continuation-object 'length) 返回自由变量的数量。

(continuation-object 'ref n) 返回一个审查器对象,包含 continuation 的第 n 个自由变量。详见下面的变量审查器的部分。n必须是非负数且小于过程中自由变量的数量。

(continuation-object 'eval expr)expr 求值并返回其值。continuation 帧中的自由变量在被求值的表达式中使用标识符%n 表示,n 是审查器显示的位置数字。具名的变量同样也绑定到他们本身的名字上。

(continuation-object 'code) 返回一个审查器对象,返回过程的代码对象,此代码是当前的 continuation 帧被创建时对应过程是正在运行的。详见下文有关代码审查器对象。

(continuation-object 'depth) 返回当前 continuation 包含的帧的数量。

(continuation-object 'link) 返回一个审查器对象,其中包含下一个 continuation 帧。深度必须大于 1。

(continuation-object 'link* n) 返回一个审查器对象,其中包含 continuation 的第 n 个链接,n 必须小于深度。

(continuation-object 'source) 返回一个审查器对象,其中包含与 continuation 相关的源代码的信息(程序中导致 continuation 形成的源码)。如果没有附加源码信息那么返回 #f

(continuation-object 'source-object) 返回一个审查器对象,其中包含产生 continuation 形成相关的源码对象,如果没有附加源码对象那么返回 #f

(continuation-object 'source-path) 尝试寻找包含产生 continuation 过程所在的源码文件的路径。如果找到了就用三个值来标记文件内的位置: pathline 以及 char 。如果文件名知道但是对应名字的文件找不到就返回两个值,文件名和字符的绝对位置。如果文件在编译后被修改了,即使找到了该文件依然是作为搜索失败处理。如果不知道文件名那么没有返回值。参数 source-directories 决定了使用相对路径时源文件的搜索目录。

Code(代码) 审查器对象 一个审查器对象包含 Chez Scheme 的代码对象

(code-object 'type) 返回一个符号 code

(code-object 'name) 返回一个字符串或者 #f 。代码审查器对象的名字就是对应过程原本绑定或者是被赋值时的名字。因为变量的绑定可以被改变所以这个名字可能会对不上。 如果审查器不能获悉过程的名字就返回 #f

(code-object 'source) 返回代码对象附加的源码信息,如果没有源码信息就返回 #f。

(code-object 'source-object) 返回一个审查器对象,其中包含代码对象的源对象,如果没有附加源对象则返回 #f。

(code-object 'source-path) 尝试寻找包含产生产生对应过程的 lambda 表达式所在的源文件的文件名。如果找到了就用三个值来标记文件内的位置: pathline 以及 char 。如果文件名知道但是对应名字的文件找不到就返回两个值,文件名和字符的绝对位置。如果文件在编译后被修改了,即使找到了该文件依然是作为搜索失败处理。如果不知道文件名那么没有返回值。参数 source-directories 决定了使用相对路径时源文件的搜索目录。

(code-object 'free-count) 返回对应代码的任意过程中自由变量的数量。

Variable(变量)审查器对象 。 变量审查器对象封装了变量绑定。尽管真正的底层表示可能会不同,变量审查器对象提供了统一的界面。

(variable-object 'type) 返回符号 variable

(variable-object 'name) 返回符号或者 #f 。如果无法获得名字或者名字是编译器生成的临时变量则返回 #f 。如果在编译时期 generate-inspector-information 为假则变量名称不会被保留。

(variable-object 'ref) 返回一个审查器对象,其中包含变量的当前值。

(variable-object 'set! e) 将当前的变量值设定为 e , 返回值不确定。如果变量不能被赋值则抛出一个状态类型为&assertion 的异常。

Port(端口) 审查器对象 包含端口的端口审查器对象。

(port-object 'type) 返回符号 port

(port-object 'input?) 如果一个端口是输入端口则返回 #t ,否则返回 #f

(port-object 'output?) 如果一个端口是输出端口则返回 #t ,否则返回 #f

(port-object 'binary?) 如果一的端口是二进制端口则返回 #t ,否则返回 #f

(port-object 'closed?) 如果端口关闭了则返回 #t ,如果开着就返回 #f

(port-object 'name) 返回一个审查器对象包含端口的名字。

(port-object 'handler) 返回一个过程审查器对象,其中封装了端口的句柄,就像 port-handler 会返回的那样。

(port-object 'output-size) 如果是一个输出端口的话返回一个表示输出缓冲区大小的定长数,否则的话返回值是不确定的。

(port-object 'output-index) 如果是一个输出端口则将缓冲区下标作为一个定长数返回,否则是个不确定的值。

(port-object 'output-buffer) 返回一个审查器对象,其中包含用于输出缓冲的字符串。

(port-object 'input-size) 如果是一个输入端口的话返回一个输出缓冲区大小的定长数,否则的话返回值是不确定的。

(port-object 'input-index) 如果是一个输入端口则返回一个输入缓冲区大小的定长数,否则返回值不确定。

(port-object 'input-buffer) 返回一个审查器对象 ,其中包含用于输入缓冲的字符串。

Symbol(符号)审查器对象 。 符号审查器对象包含符号,包活 gensyms(生成符号)

(symbol-object 'type) 返回符号 symbol

(symbol-object 'name) 返回一个字符串审查器对象。一个符号审查器的的字符串表示的名字就是该符号的被打印时的表示。就像过程symbol->string 会返回的那样。

(symbol-object 'gensym?) 如果一个符号是生成符号则返回 #t ,否则返货 #f 。生成符号使用 gensym 生成。

(symbol-object 'top-level-value) 返回一个审查器对象 ,其中包含符号的全局值。

(symbol-object 'property-list) 返回一个审查器对象,其中包含符号的属性列表。

Record(记录)审查器对象 。记录审查器对象中包含记录。

(record-object 'type) 返回符号 record

(record-object 'name) 返回一个字符串审查器对象,其中包含记录类型的名字。

(record-object 'fields) 返回一个审查器对象,其中包括记录类型的成员名字列表。

(record-object 'length) 返回数据成员的数量。

(record-object 'rtd) 返回一个审查器对象,其中包含记录类型的记录类型描述符。

(record-object 'accessible? name) 如果对应名字的数据成员是可以访问的就返回 #t ,否则返回 #f 。如果编译器优化过数据成员有可能不能被访问。

(record-object 'ref name) 返回一个审查器对象,其中包括对应名字数据成员的值。如果对应数据成员不可访问则会抛出状态类型为&assertion 的异常。

(record-object 'mutable? name) 如果对应名字的数据成员是可以被改变的则返回 #t ,否则返回 #f 。如果一个数据成员没有申明为可改变的那么就是不可更改的。如果编译器将所有的数据成员的赋值都优化掉了那么他们也是不可更改的。

(record-object 'set! name value) 将对应名字的数据成员设置为 value 。如果一个数据成员不能被赋值就抛出状态类型为 &assertion 的异常。

3.5 定位对象

  • 过程: (make-object-finder pred)
  • 过程: (make-object-finder pred g)
  • 过程: (make-object-finder pred x g)
  • 返回: 见下文
  • 所属库: (chezscheme)

过程 make-object-finder 接受一个谓词过程 pred 以及两个可选参数: 一个开始点 x 和一个最大代数 g 。开始点的位置默认是过程 oblist 的值,最大代数的默认值由参数 collection-maximum-generation 决定。 make-object-finder 返回一个对象检索器p,可以用来搜寻从起始点 x 开始,满足 pred 的对象。立即对象和世代比 g 大的对象会被视为叶子节点。 p 是一个不接受参数的过程。如果从 x 开始能找到一个满足 pred 的对象 y, p 就会返回一个列表,列表的第一个值是 y,剩下的元素代表从 x 开始到 y 的途径对象,列表是逆序的。 p 可以被调用多次来找到其他满足满足谓词过程的对象,如果有的话。如果没有更多满足条件的对象就返回 #f

p 会在维护一个内部状态用于记录它搜索过哪些地方,这样当搜索再次开始的时候他不会两次返回同一个对象。这个状态可以是起始点对象和所有能从 x 访问的对象的好几倍。

交互式的审查器提供了 findfind-next 这些方便的方式来寻找对象。

默认情况下,静态代码的重定位表是被丢弃掉的,这会造成对象查找器在涉及到静态代码对象时无法提供准确的结果。也就是说,无法直接从已经提升为静态生成的代码中找到任何代码对象。 如果这是一个问题,可以使用命令行参数 --retain-static-relocation防止重定位表被丢弃。

3.6 嵌套对象的大小和组成

过程 compute-sizecompute-composition 可以用于知晓一个对象的大小或组成,包括任何从该对象可访问的对象。根据从对象可到达的对象数量,这些过程可能会消耗大量内存。在了解堆中所有对象的数量,大小,代数和类型的应用程序中, object-counts 可能效率更高。

这些过程将立即对象(如 定长数,布尔值和字符)视为不计数,零字节的叶子节点。

默认情况下,这些过程还将静态对象(初始堆中的对象)视为不计数,零字节的叶子节点。 两个过程都接受一个可选的第二个参数,该参数指定计算时最大的代数,其中符号 static 用于表示静态代。

对象有时会指向比预期更多的对象。例如,如果将静态数据包含在内,过程 (lambda (x) x) 会间接指向异常处理系子系统(由于参数数量检查),以及其他诸如此类的对象。

默认情况下,静态代码的重定位表是被丢弃掉的,这会造成对象查找器在涉及到静态代码对象时无法提供准确的结果。也就是说,无法直接从已经提升为静态生成的代码中找到任何代码对象。 如果需要静态代码对象的精确大小和组成的话,使用命令行参数 --retain-static-relocation防止重定位表被丢弃。

  • 过程: (compute-size object)
  • 过程: (compute-size object generation)
  • 返回: 见下文
  • 所属库: (chezscheme)

object 可以是任意对象, generation 必须是一个 0 到 collect-maximum-generation 之间的定长数,或者符号 static。如果 generation 没有指定,默认就是 collect-maximum-generation 的值。 compute-size 返回 object 以及从它开始可访问的所有代数小于等于 generation 的对象在内存中占据的以字节计算的大小。立即值例如定长数,布尔值以及字符的大小为 0。

以下例子适用于 32 位指针的机器。

(compute-size 0) => 0
(compute-size (cons 0 0)) => 8
(compute-size (cons (vector #t #f) 0)) => 24 

(compute-size
  (let ([x (cons 0 0)])
    (set-car! x x)
    (set-cdr! x x)
    x))                  => 8 

(define-record-type frob (fields x))
(collect 1 1) ; force rtd into generation 1
(compute-size
  (let ([x (make-frob 0)])
    (cons x x))
  0)                       => 16
  • 过程: (compute-composition object)
  • 过程: (compute-composition object generation)
  • 返回值: 见下文
  • 所属库: (chezscheme)

object 可以是任意对象。 generation 必须是一个 0 到 collect-maximum-generation 之间的定长数,或者符号 static。如果 generation 没有指定,默认就是 collect-maximum-generation 的值。 compute-composition 返回一个关联列表用于表示 object 的组成 , 包括任何从当前对象开始可以访问的代数小于或等于generation 的对象 。关联列表的结构如下:

((type count . bytes) ...)

type 是原始类型的名字(使用符号表示,例如 pair )或是一个记录类型描述符(rtd)。 countbytes 都是非负定长数。

立即对象例如常数、布尔值和字符并不包括在组成内。

以下例子适用于 32 位指针的机器。

(compute-composition 0) => ()
(compute-composition (cons 0 0)) => ((pair 1 . 8))
(compute-composition
  (cons (vector #t #f) 0)) => ((pair 1 . 8) (vector 1 . 16)) 

(compute-composition
  (let ([x (cons 0 0)])
    (set-car! x x)
    (set-cdr! x x)
    x))                 => ((pair 1 . 8) 

(define-record-type frob (fields x))
(collect 1 1) ; force rtd into generation 1
(compute-composition
  (let ([x (make-frob 0)])
    (cons x x))
  0)                       => ((pair 1 . 8)
                                (#<record type frob> 1 . 8))

results matching ""

    No results matching ""