230
社区成员




这次作业因为期末考试和新冠耽搁了好久,现在终于有空给它做出来了……
作业内容描述:
对于任务1和任务3,课程给出的示例代码已经完成了,这里就简要分析一下它的实现。
encode函数的重点在于Label
伪指令的处理,它不需要生成字节码,只是用于指示GOTO
指令的目标位置。示例代码的实现在碰到Label
指令直接无操作,而在编码GOTO
时查找标签的字节码序号,然后计算从序号到GOTO
指令之间生成的字节码长度,将这个长度输出到字节码序列中,因此解释器在处理GOTO
指令时,跳转目标是相对当前程序指针寻址的。
示例代码实现的任务3编译器和课上讲解的基本一致,只是为了处理递归函数专门为AST引入了一个Self
结构:
type prim = Add | Mul | Self
type rec expr =
| Cst(int)
| Var(string)
| Let(string, expr, expr)
| Letfn(string, list<string>, expr, expr) // fnName args body let-scope
| App(string, list<expr>)
| Prim(prim, list<expr>)
| If(expr, expr, expr)
我个人认为这么做并不能完全处理所有递归调用的情况,但是出于尊重示例代码、避免修改示例代码的考虑,权且认为它完成任务了。
和课程的区别在于:为了遵守 C++ 语言的类型规范,我在实现解释器时将程序链接和数据分开使用了两个栈,然后发现字节码里Call
指令的参数个数元信息在我这里是多余的。
这里是示例代码 expr
的虚拟机代码:
open Expr
module List = Belt.List
module HMapS = Belt.HashMap.String
type rec value = Int(int) | Closure(envs, list<string>, expr)
and env = HMapS.t<value>
and envs = list<env>
let rec print = (value: value) => {
switch value {
| Int(i) => Js.log(`Int(${Belt.Int.toString(i)})`)
| Closure(a, b, c) => {
Js.log("Closure(")
Js.log(a)
Js.log(",")
Js.log(b)
Js.log(",")
Js.log(c)
Js.log(")")
}
}
}
let rec print_envs = (envs: envs) => {
switch envs {
| list{} => Js.log(None)
| list{env, ...rest} => {
Js.log(env)
print_envs(rest)
}
}
}
let rec resolve_helper = (envs: envs, name: string) => {
switch envs {
| list{} => None
| list{env, ...rest} => {
let val = HMapS.get(env, name)
if val == None { resolve_helper(rest, name) } else { val }
}
}
}
let resolve = (envs: envs, name: string): value => {
let v = resolve_helper(envs, name)
switch v {
| Some(t) => t
| None => {
Js.log(name)
print_envs(envs)
assert false
}
}
}
let rec eval = (expr: expr, envs: envs): value => {
let list{env, ..._} = envs
switch expr {
| Cst(n) => Int(n)
| Var(s) => resolve(envs, s)
| Let(name, e1, e2) => {
HMapS.set(env, name, eval(e1, envs))
eval(e2, envs)
}
| Letfn(name, args, body, scope) => {
HMapS.set(env, name, Closure(envs, args, body))
eval(scope, envs)
}
| App(name, args) => {
let closure = resolve(envs, name)
let Closure(cenvs, cargs, cexpr) = closure
let nenv = HMapS.make(~hintSize=1)
List.forEach2(cargs, args, (a, b) => HMapS.set(nenv, a, eval(b, envs)))
HMapS.set(nenv, "@", closure) // 上下文环境里的 "@" 就指向当前调用的函数
eval(cexpr, list{nenv, ...cenvs})
}
| Prim(op, es) => {
switch op {
| Add => {
let list{a, b, ..._} = es
let Int(va) = eval(a, envs)
let Int(vb) = eval(b, envs)
Int(va + vb)
}
| Mul => {
let list{a, b, ..._} = es
let Int(va) = eval(a, envs)
let Int(vb) = eval(b, envs)
Int(va * vb)
}
| Self => {
let closure = resolve(envs, "@")
let Closure(cenvs, cargs, cexpr) = closure
// 上下文环境里的 "@" 就指向当前调用的函数
let nenv = HMapS.make(~hintSize=1)
HMapS.set(nenv, "@", closure)
List.forEach2(cargs, es, (a, b) => HMapS.set(nenv, a, eval(b, envs)))
eval(cexpr, list{nenv, ...cenvs})
}
}
}
| If(e1, e2, e3) => {
let Int(v1) = eval(e1, envs)
if (v1 != 0) { eval(e2, envs) } else { eval(e3, envs) }
}
}
}
感觉基本写法和之前实现的lambda演算解释器差不多,就是在解释Self
时需要稍稍处理一下。