tutorial. Scheme解释器的多文件工程[lp-000A]
一个程序可以由多篇文章解释,也可以输出到多个文件。两种划分不必一一对应:解释器的求值规则、闭包与环境提供片段,本页把同一份定义组织成Guile模块。它不是复制出来的另一套解释器。
Scheme工程的文件与阅读关系[#63]
环境、原语、求值器、入口与测试各有模块边界。evaluate与apply-value互相调用,所以进入同一个engine模块;内部递归不变成两个文件之间的循环导入。阅读仍可分别讨论求值和闭包,不需要按文件目录排列。
(make-program "mce-primitives-module" "Scheme原语模块"
(list primitives-fragment) '(initial-environment)
#:imports (list (make-import '(lp-example mce environment)
'(extend-environment))))make-import声明实际导入的模块与名称,make-module-output将计划映射到Guile模块路径。make-project核验项目内的导出、重复身份和模块顺序;它不自动排序,也不猜测自由变量。外部模块在实例化或独立运行时由Guile解析。
project. Scheme解释器的模块工程[mce-project]
Module order: 文件按显式依赖顺序声明;每个Module拥有独立的导入和导出。
module. Scheme环境模块[mce-env-module]
Outputs: lp-example/mce/environment.scm
Checks: Scheme工程的11项值与5项异常核验
Exports: (extend-environment lookup define-value!)
Initialization order: 片段按下列清单建立;函数体依赖可以相互引用。
module. Scheme原语模块[mce-primitives-module]
Outputs: lp-example/mce/primitives.scm
Checks: Scheme工程的11项值与5项异常核验
Imports: (lp-example mce environment) (extend-environment)
Exports: (initial-environment)
Initialization order: 片段按下列清单建立;函数体依赖可以相互引用。
module. Scheme求值与闭包应用模块[mce-engine-module]
Outputs: lp-example/mce/engine.scm
Checks: Scheme工程的11项值与5项异常核验
Imports: (lp-example mce environment) (extend-environment lookup define-value!)
Exports: (evaluate-sequence)
Initialization order: 片段按下列清单建立;函数体依赖可以相互引用。
module. Scheme解释器入口模块[mce-entry-module]
Outputs: lp-example/mce/entry.scm
Checks: Scheme工程的11项值与5项异常核验
Imports: (lp-example mce primitives) (initial-environment)
Imports: (lp-example mce engine) (evaluate-sequence)
Exports: (run)
Initialization order: 片段按下列清单建立;函数体依赖可以相互引用。
module. Scheme工程核验模块[mce-checks-module]
Outputs: lp-example/mce/checks.scm
Checks: Scheme工程的11项值与5项异常核验
Imports: (lp-example mce entry) (run)
Exports: (run-checks)
Initialization order: 片段按下列清单建立;函数体依赖可以相互引用。
Module的Exports是公开入口,Imports链接到提供名称的项目模块,Initialization order是定义建立顺序。Outputs直接打开生成文件。片段上的Assembled in列出所有使用它的计划,包含原来的单文件计划和本页的模块计划;这些信息来自装配数据,不需要另写索引。
Scheme工程的模块隔离与构建核验[#64]
instantiate-project为每个计划创建独立命名空间,只连接显式导出的binding;项目实例不会注册到宿主的全局模块表。不同项目实例有自己的本地定义,但外部导入的模块状态仍遵循Guile的共享规则。
(define instance (instantiate-project interpreter-project)) (define run (project-binding instance '(lp-example mce entry) 'run)) (run '((+ 1 2)))
核验案例复用11个值案例和5个异常案例的实际数据;输出的是读取时的数据快照,不导入Flora模块。这里核验多模块解释器本身,自解释仍由同源自解释案例检查。
program. Scheme工程复用的值与异常案例[mce-project-cases]
Defines: (examples errors)
Assembled in Scheme工程核验模块
Outputs: lp-example/mce/checks.scm
(define examples
'((factorial
((define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))
(factorial 6))
720)
(lexical
((define x 10)
(define add-x (lambda (y) (+ x y)))
((lambda (x) (add-x 2)) 100))
12)
(closure
((define (make-adder x) (lambda (y) (+ x y)))
(define add-three (make-adder 3))
(add-three 4))
7)
(lazy-if ((if #t 7 missing)) 7)
(truth ((if 0 1 2)) 1)
(mapping ((map (lambda (x) (* x x)) '(1 2 3 4))) (1 4 9 16))
(application ((apply + '(1 2 3))) 6)
(named-let
((let loop ((n 5) (total 0))
(if (= n 0) total (loop (- n 1) (+ total n)))))
15)
(let-star ((let* ((x 3) (y (+ x 4))) (* x y))) 21)
(short-circuit ((list (and #f missing) (or 9 missing))) (#f 9))
(conditional ((cond ((= 1 2) missing) ((= 2 2) 8) (else 0))) 8)))
(define errors
'((missing (missing) mce-unbound)
(arity (((lambda (x) x) 1 2)) mce-arity)
(call ((1 2)) mce-not-procedure)
(syntax ((if #t 1)) mce-syntax)
(parameters ((lambda (x x) x)) mce-syntax)))
program. Scheme工程的独立核验入口[mce-project-checks]
Defines: (caught-key run-checks)
Assembled in Scheme工程核验模块
Outputs: lp-example/mce/checks.scm
Requires: run, examples, errors
(define (caught-key thunk)
(catch #t (lambda () (thunk) 'returned)
(lambda (key . arguments) key)))
(define (run-checks)
(for-each
(lambda (example)
(unless (equal? (run (cadr example)) (caddr example))
(error "Result mismatch" (car example)))) examples)
(for-each
(lambda (example)
(unless (eq? (caught-key (lambda () (run (cadr example)))) (caddr example))
(error "Exception mismatch" (car example)))) errors)
(list (length examples) (length errors)))example. Scheme工程的11项值与5项异常核验[mce-project-result]
(run-project-checks)⇒ (11 5) · checked
program-check将已经核验的结果tree关联到被测计划,使各Module的Checks指向这项证据;它本身不增加执行,也不自动推断测试覆盖范围。
Scheme工程的源码下载与独立运行[#65]
Download lp-example/mce/environment.scm
Download lp-example/mce/primitives.scm
Download lp-example/mce/engine.scm
Download lp-example/mce/entry.scm
Download lp-example/mce/checks.scm
五个.scm文件保留模块目录结构。Guile模块名(lp-example mce entry)对应lp-example/mce/entry.scm;生成文件中的define-module、导入和导出来自计划,定义来自原片段。下载单个文件并不包含依赖模块,独立运行需要保留这套目录。
在Flora根目录运行以下命令即可核验生成工程;这里不需要Grove或Flora模块:
guile --no-auto-compile -L build/programs -c '(use-modules (lp-example mce checks)) (write (run-checks)) (newline)'
成功输出(11 5),错误会以非零状态退出。也可用(use-modules (lp-example mce entry))取得run,用普通Scheme程序调用解释器。本实验面向Guile模块,不声称兼容其他Scheme实现。
LP依赖声明与初始化错误[#66]
Requires描述函数运行等阶段需要的名称;Initial requires描述整个片段开始建立时已经需要的名称。例如保存初始值的片段必须排在使用这个值的初始化片段之前:
(code-fragment "seed" "初始值" (define seed 3)) (code-fragment "answer" "初始化时读取初始值" #:initial-requires (seed) (define answer (+ seed 1)))
顺序写反时,诊断包含计划、片段、缺少的名称与来源位置。重复定义会指出两个提供者及其来源;导入与本地定义重名、项目内名称未导出也会被拒绝。Initial requires是作者声明的片段级屏障,不是自动分析器:同一片段内部的初始化顺序、未声明依赖和运行时异常仍由执行核验发现。
以下实际片段展示Initial requires如何链接到提供者;正确顺序的实例得到4:
program. LP初始化示例的初始值[lp-seed]
program. LP初始化示例的依赖值[lp-answer]
module. LP初始化依赖的装配计划[lp-initialization]
example. LP显式初始化依赖的结果为4[lp-initialization-result]
(instance-binding (instantiate-program initialization-plan) 'answer)⇒ 4 · checked
错误顺序只构造计划就会被拒绝,不需要等到运行。这里捕获诊断以便阅读;普通构建中不捕获这个异常时,构建会失败:
misc-error: (#f "~A ~S ~S ~S ~S" ("Initialization dependency must precede fragment" "lp-reversed" "lp-answer" seed ((file . "/home/jinser/vie/writing/flora/plants/lp-000A.plant") (line . 83) (column . 6) (end-line . 83) (end-column . 32) (start-offset . 3916) (end-offset . 3942))) #f)