这次 Week 2 的作业比较难,任务目标是使用 \(racket\) 给一个虚拟语言 \(MUPL\) (made-up programming language) 写一个解释器

所以单独开个贴来好好分析一下

首先是 MUPL 语言的几个 semantic,已经通过 \(racket\) struct 的形式提供

(struct var  (string) #:transparent)  ;; a variable, e.g., (var "foo")
(struct int (num) #:transparent) ;; a constant number, e.g., (int 17)
(struct add (e1 e2) #:transparent) ;; add two expressions
(struct ifgreater (e1 e2 e3 e4) #:transparent) ;; if e1 > e2 then e3 else e4
(struct fun (nameopt formal body) #:transparent) ;; a recursive(?) 1-argument function
(struct call (funexp actual) #:transparent) ;; function call
(struct mlet (var e body) #:transparent) ;; a local binding (let var = e in body)
(struct apair (e1 e2) #:transparent) ;; make a new pair
(struct fst (e) #:transparent) ;; get first part of a pair
(struct snd (e) #:transparent) ;; get second part of a pair
(struct aunit () #:transparent) ;; unit value -- good for ending a list
(struct isaunit (e) #:transparent) ;; evaluate to 1 if e is unit else 0 ;; a closure is not in "source" programs but /is/ a MUPL value; it is what functions evaluate to
(struct closure (env fun) #:transparent)

我们用 eval-exp 函数对上面的 semantic 进行解释

(define (eval-exp e)
(eval-under-env e null))

eval-under-env 函数则是在提供的 \(env\) 环境下对表达 \(e\) 进行解释,也是我们需要实现的主要函数

这里,环境 我们用 \(racket\) pair 组成的 list 表示,每个 (string, val) 组成的 pair 中,string 为变量名,而 val 为变量值

另外作业中还提供了 envlookup 函数,提供变量名 string,返回环境中对应的变量值

(define (envlookup env str)
(cond [(null? env) (error "unbound variable during evaluation" str)]
[(equal? (car (car env)) str) (cdr (car env))]
[#t (envlookup (cdr env) str)]))

在开始分析之前,需要强调一下 \(MUPL\) 语言是一门函数式语言,一个表达 (expression) 是由若干个表达 (subexpression) 嵌套而成

在所有提供的 semantic 中,可以产生 binding 的只有 mlet (let 表达定义的本地变量 binding),call (call 中传入的值 call-actual 与对应函数的参数 fun-formal 进行 binding) 以及 fun (函数名 fun-nameopt 与函数)

接下来我们一个一个分析对应的 case:

  1. \(var\)

var 中存储的是一个变量的名字,对其解释的结果应该是这个变量的值,所以

[(var? e) (envlookup env (var-string e))]
  1. \(int\), \(aunit\) 与 \(closure\)

对于 int,其储存的是一个 int 类型的数字,可以算作是所谓最基本的表达,无需再进行解释

aunitclosure 类似。如果说 int 是 int 类型变量的,那么 closure 可以说是 function 的 。所以

[(int? e) e]
[(aunit? e) e]
[(closure? e) e]
  1. \(fun\) 与 \(call\)

这里真的想了很久,关于 \(fun\) 与 \(call\) 两个表达的解释方式

由于受到线性语言的影响,自然而然会有这样的想法:在函数被定义的时候将其与对应的 closure 加入环境 (形成一个 fun-closure binding),在被调用的时候像变量一样进行解释

这样的想法是可行的,但在 \(MUPL\) 语言中,由于只提供了有限的 semantic,我们可以发现类似 函数定义形成 binding (如 ML 的 fun, Racket 的 define) 是没有对应的 semantic 的,唯一能使函数形成 binding 的地方 在 \(call\) 语句中 (在解释函数体时将 fun-closure binding 加入环境以实现函数的递归调用)

本质的来讲,就是 \(MUPL\) 语言中,函数的 第一次被定义与第一次(层)被调用是同步进行的。所以,只有第二层及以上的调用可以使用函数的名字

而在线性语言中,函数的定义和调用之间可以间隔很远,在定义时解释器就将 fun-closure binding 加入环境,之后的调用都可以直接采用名字来调用

[(fun? e) (closure env e)]  ; closure is function's value
[(call? e)
(let ([cl (eval-under-env (call-funexp e) env)]
[arg (eval-under-env (call-actual e) env)])
(if (closure? cl)
(let* ([fn (closure-fun cl)]
[bodyenv (cons (cons (fun-formal fn) arg) (closure-env cl))] ; 将 参数名-参数值对 传入解释函数体的环境中
[bodyenv (if (fun-nameopt fn) (cons (cons (fun-nameopt fn) cl) bodyenv) bodyenv]) ; 若函数具名,则将 函数-闭包对 传入解释函数体的环境,以保证递归可实现
(eval-under-env (fun-body fn) bodyenv)) ; 解释函数体的环境 = 定义函数的环境 (存储在闭包中) + 参数相关绑定 + 函数-闭包绑定
(error "MUPL funciton call with nonfunction")))]
  1. \(add\) 与 \(ifgreater\)

这个就比较简单了,比较标准的树形结构,先 interpret subexpressions 再最终 interpret expression

贴一个 addifgreater 差不多

[(add? e)
(let ([v1 (eval-under-env (add-e1 e) env)]
[v2 (eval-under-env (add-e2 e) env)])
(if (and (int? v1)
(int? v2))
(int (+ (int-num v1)
(int-num v2)))
(error "MUPL addition applied to non-number")))]
  1. \(apair\),\(fsd\) 与 \(snd\)

三个 \(pair\) 相关的 semantic,同样遵循 subexpression-expression 的规则

[(apair? e)
(apair (eval-under-env (apair-e1 e) env) (eval-under-env (apair-e2 e) env))]
[(fst? e)
(let ([pr (eval-under-env (fst-e e) env)])
(if (apair? pr)
(apair-e1 pr)
(error "MUPL fst applied to non-apair")))]
[(snd? e)
(let ([pr (eval-under-env (snd-e e) env)])
(if (apair? pr)
(apair-e1 pr)
(error "MUPL snd applied to non-apair")))]
  1. \(isaunit\)

aunit 这个 semantic 很特殊,它没有 field,因此也储存不了任何其他值

它的存在和 ML 中的 NONE 很相似

[(isaunit? e)
(let ([v (eval-under-env (isaunit-e e) env)])
(if (aunit? v) (int 1) (int 0)))]
  1. \(mlet\)

callmlet 是唯二可以向环境中添加新 binding 的 semantic

所以只要理解了 \(call\),\(mlet\) 的实现也可以说是很简单

[(mlet? e)
(let ([bodyenv (cons (cons (mlet-var e) (eval-under-env (mlet-e e) env)) env)])
(eval-under-env (mlet-body e) bodyenv))]

至此,我们已经成功编写了 \(MUPL\) 语言的解释器 eval-exp

接下来,我们要用 racket 继续 扩展 这门语言

扩展一门语言的方法就是:用元语言编写函数的方式来定义宏 (Defining "Macros" via functions in metalanguage)

记得一定要分清被实现语言 (language being implemented) 与元语言 (metalanguage) 的区别

也就是说,在用编写函数的方式来定义被实现语言的宏时,不能用到元语言本身的 semantic (包括 eval-exp)

  1. (ifaunit e1 e2 e3)

若 \(e1\) evaluate 为 aunit,则返回 \(e2\),否则返回 \(e3\)

; 错误例子1
(define (ifaunit e1 e2 e3)
(if (aunit? e1) e2 e3)) ; 这样定义的 macro 是错误的,因为用到了元语言的 semantic "if"
; 正确写法
(define (ifaunit e1 e2 e3)
(ifgreater (isaunit e1) (int 0) e2 e3)) ; 这里用到的所有 semantic (ifgreater, isaunit) 都是 MUPL 语言中的
  1. (mlet* bs e2)

初始环境为空,按顺序 evaluate bs 中的每一个 (var-val) 对并添加入环境,最后用该环境 evaluate e2

(define (mlet* bs e2)
(ifaunit bs
e2
(mlet (fst (fst bs)) (snd (fst bs)) (mlet* (snd bs)))))
  1. (ifeq e1 e2 e3 e4)

若 \(e1\) 与 \(e2\) evaluate 出的值一致,则返回 \(e3\),否则返回 \(e4\)

且保证 \(e1\) 与 \(e2\) 只被 evaluate 一次

(define (ifeq e1 e2 e3 e4)
(mlet "_x" e1
(mlet "_y" e2 ; 定义临时变量,保证 e1 与 e2 都只被 evaluate 一次
(ifgreater (var "_x") (var "_y") e4
(ifgreater (var "_y") (var "_x") e4 e3)))))
  1. mupl-map

将名为 mupl-map 的 \(Racket\) 函数与一个 \(MUPL\) 函数绑定

这个函数 takes 一个函数 \(a\),返回一个函数 \(b\)

函数 \(b\) takes 一个 list,并且将 \(a\) 对 list 的每个元素应用,最后返回新 list (其实就是 map)

(define mupl-map
(fun #f "a"
(fun "b" "ls"
(ifaunit (var "ls")
(aunit)
(apair (call (var "a") (fst (var "ls")))
(call (var "b") (snd (var "ls"))))))))

注意,在调用函数时,第一个参数 funexp 是整个函数本身,所以我们用 (var "fun_name") 进行传入

这样 evaluate 出来的结果也是对应的 closure,符合 evaluate function 后得到的结果

  1. mupl-mapAddN

同上,函数 takes 一个整数 \(i\) 返回一个函数 \(f\)

这个函数 \(f\) takes 一个 list,并将 list 中的每个元素都加上 \(i\)

(define mupl-map
(fun #f "i"
(call mupl-map (fun #f "x" (add (var "x") (var "i"))))))

最新文章

  1. [20160731][转]JAVA当中变量什么时候需要初始化
  2. JavaScript中this和$(this)之间的区别以及extend的使用
  3. 计算几何--判断两条线段相交--poj 2653
  4. MYSQL随机抽取查询 MySQL Order By Rand()效率问题
  5. php 缓存加速器软件
  6. 【 D3.js 高级系列 — 5.0 】 颜色
  7. 【ZOJ】3430 Detect the Virus
  8. linux vim 个性化设置(.vimrc)
  9. js兼容性大全(持续更新)
  10. 最全的Swift社交应用文本输入优化汇总
  11. WebService和AngularJS实现模糊过滤查询
  12. Fiddler设置代理(PC和Android)
  13. ZOJ2402 Lenny's Lucky Lotto List 简单DP
  14. python contextlib 上下文管理器
  15. NemaStudio船舶模拟软件下载及破解
  16. 通过Log4net来配置我们需要的日志文件格式
  17. Kubernetes资源管理
  18. DFSMN结构快速解读
  19. PostgreSQL进程和内存结构
  20. [leetcode]133. Clone Graph 克隆图

热门文章

  1. mongodb下载和安装
  2. reduced form(简化式)和structural form(结构式)
  3. Python的入门学习Day 10——form”夜曲编程“
  4. GIT Authentication failed for错误问题处理
  5. 深度剖析CPython解释器》Python内存管理深度剖析Python内存管理架构、内存池的实现原理
  6. COOP/COHP(上)-PROOUT
  7. 《Spring Boot从零开始学(视频教学版)》快速入门Spring Boot应用开发
  8. java面试准备基础篇
  9. prometheus-alertmanager 告警规则
  10. 制作docker php5.6的镜像