Licp - 一个玩具解释器的实现
纸上得来终觉浅,绝知此事要躬行。
最近看了 SICP,其第四章讲述了一个简单的 Scheme 解释器的实现。粗看了一遍后决定自己用 C 语言实现一个残疾的 Scheme 解释器,想来这样的学习效果应该比只看书要强得多。
在这过程中发现用 C 实现这样一个解释器比用 Lisp 写要麻烦得多。一个 Lisp 语句本身其实就是一个 Lisp 里的表,利用这一特性就可以省去词法分析划分 token 的步骤,直接用自带的 car
和 cdr
等过程就能方便地提取出语句中的各个成分(相当于直接对树操作);而用 C 就只能老老实实地处理字符串,从而生成一个树结构。
我瞎 YY 出来的这种树结构有点 AST 的样子,暂且把它叫做语法树吧:
struct _Node {
char *exp;
struct _Node *next;
struct _Node *child;
};
next
代表右边的兄弟节点,child
则是子节点。对于复杂语句(也就是能够继续往下划分的节点)不需要记录其表达式字符串(因为并没有什么卵用),而对于 atom (也就是不能继续划分的元素)则用 exp
记录其字符串。
对于这样一个语句((lambda (x) (+ x 1)) 2)
,对应的语法树应该是这样的:
然后就是 eval
和 apply
的互相调用。
Scheme 有个特点是把函数作为一级对象,也就是和数据同等对待,所以设计 eval
函数的时候应该考虑到其可以返回数值和函数(当然还有符号和表)。于是应该设计一个 Value
结构体,使得其代表一种通用的值类型,既可以存储数值也可以存储过程。
eval
的实现中关键的部分在于判断语句的类型,这里的一大串 if..else
的顺序非常重要,大体框架应该是这样的:
if (node->child) {
node = node->child;
if (isPrimitive(node->exp)) {
return evalPrimitive(node, env);
} else if (isLambda(node->exp)) {
if (node->exp)
return evalLambda(node, env);
} else { //apply procedure
return apply(node, env);
}
} else { //self-evaluating expressions
if (isNumber(node->exp)) {
return (Value){NUMBER, atoi(node->exp)};
} else {
return getValue(node->exp, env);
}
}
其逻辑概括起来就是对于复杂表达式应该先判断其语句类型(Primitive 表示 Scheme 原有的那些操作比如算术操作),如果不属于任何类型则必然是对函数的执行;而除了复杂表达式剩下的就是自求值表达式,判断是字面量还是变量,并做相应处理。
这里涉及到求值所在的环境。环境可以用一个个嵌套的框架来表示,表示不同的作用域。每个框架有一个 parent
指针指向外围框架。对变量的定义、赋值和取值都通过维护环境框架中的一个变量列表来实现。
而 apply
函数则为函数的执行创造一个环境框架,并将形参与实参进行对应。然后再调用 eval
在新的环境里对函数体求值。
然后一个基本的 Scheme 解释器就完成了:
这个项目托管在 GitHub 上: http://github.com/lsdsjy/licp
目前只实现了 lambda
和一些关于整数的 Primitive 操作,而 define
和条件语句尚待实现。期待最后做出一个 IDE。可以预见又是一个大坑。
Licp - 一个玩具解释器的实现的更多相关文章
- 用C++实现一个Brainfuck解释器
Brainfuck是一种极小化的计算机语言,只含有8种运算符,由于fuck在英语中是脏话,这种语言有时被称为brainfck或brainf**,甚至被简称为BF.正如它的名字所暗示,brainfuck ...
- 打包一个python解释器
利用python的exec语句,可以很方便地动态执行python语句.如果一个python代码打包为了exe,其原先的代码就很难更改了.一个好的解决方法就是import相应的库,然后把主程序段放到一个 ...
- 前端与编译原理——用JS写一个JS解释器
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...
- 一个玩具程序——测试密码强度(pure C)
替人写的C语言作业… 介绍: 程序名称:密码强度检测程序 注释风格:doxygen 测试环境:linux3.6, gcc4.7window7, vs2012 已知问题:1. 算法与参考链接不一致,结果 ...
- 以鶸ice为例,手撸一个解释器(一)明确目标
代码地址 # HelloWorld.ice print("hello, world") 前言(废话) 其实从开始学习编译原理到现在已经有快半年的时间了,但是其间常常不能坚持看下去龙 ...
- Python之路【第二十四篇】:Python学习路径及练手项目合集
Python学习路径及练手项目合集 Wayne Shi· 2 个月前 参照:https://zhuanlan.zhihu.com/p/23561159 更多文章欢迎关注专栏:学习编程. 本系列Py ...
- python项目练习地址
作者:Wayne Shi链接:http://www.zhihu.com/question/29372574/answer/88744491来源:知乎著作权归作者所有,转载请联系作者获得授权. 目前是3 ...
- python项目推荐(转载知乎)
作者:Wayne Shi链接:https://www.zhihu.com/question/29372574/answer/88744491来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商 ...
- 第二弹:超全Python学习资源整理(进阶系列)
造一个草原要一株三叶草加一只蜜蜂.一株三叶草,一只蜂,再加一个梦.要是蜜蜂少,光靠梦也行. - 狄金森 "成为编程大牛要一门好语言加一点点天分.一门好语言,一点点天分,再加一份坚持.要是天分 ...
随机推荐
- kylin入门到实战:cube详述
版权申明:转载请注明出处. 文章来源:http://bigdataer.net/?p=306 排版乱?请移步原文获得更好的阅读体验 1.什么是cube? cube是所有dimession的组合,每一种 ...
- Thinkphp5 模块的自动生成
首先到根目录下的build.php文件中去 是这样子滴: 然后去public目录中的index.php中去添加代码 这样子: 然后运行项目 就搞定了. 是不是美滋滋! 在public 下index.p ...
- 从Github上轻松安装R包—githubinstall包--转载
1.综述 越来越多的R包正在由世界上不同的人所创建,其中一部分原因是devtools包使得开发R包1变得更加简单.devtools包不仅让开发R包变得简单,而且用于分发R包. 当开发者发布一个R包的时 ...
- vue-cli router的使用
用了很久这个vue-cli到现在连入门都算不了,为了防止忘记还是很有必要记一下随笔的. 关于vue-cli中的router的使用,, 我将所有页面都存放在components文件夹下, 灰后通过rou ...
- svn 教程
1.将文件checkout到本地目录 svn checkout path(path是服务器上的目录) 例如:svn checkout svn://192.168.1.1/pro/domain ...
- Android真机调试——远程主机强迫关闭了一个现有的连接。
以前用真机调试程序的时候,Android Studio 出现如下的错误 [2016-11-12 10:37:36 - DeviceMonitor] Adb connection Error:远程主机强 ...
- (3) iOS开发之UI处理-UIView篇
在UIView作为许多子控件的容器的时候,首先我们需要动态的计算出UIView下的所有子控件的高度,并布局排列好,然后我们还要把作为容器的UIView的高度调整到刚好包裹着所有子控件,不会过矮,也不会 ...
- hdu 4679 Terrorist’s destroy 树的直径+dp
题意:给你一棵树,每条边都有值W,然后问你去掉一条边,令val = w*max(两颗新树的直径),求val最小值~ 做法,先求树的直径,然后算出直径上每个点的最长枝条长度.这样对于每一条边,假如是枝条 ...
- [转载]Java操作Excel文件的两种方案
微软在桌面系统上的成功,令我们不得不大量使用它的办公产品,如:Word,Excel.时至今日,它的源代码仍然不公开已封锁了我们的进一步应用和开发.在我们实际开发企业办公系统的过程中,常常有客户这样子要 ...
- 网页重构中区分IE6、IE7、IE8及标准浏览器的最佳方法
由于万恶的IE6和IE7,我们在页面重构时不免要对其进行各种bug修复及差异化处理.在标准浏览器中可实现的效果在IE里却有各种离奇问题,例如IE6.IE7不能良好应对的inline-block和.cl ...