对SNL语言的解释器实现尾递归优化
对于SNL语言解释器的内容可以参考我的前一篇文章《使用antlr4及java实现snl语言的解释器》。此文只讲一下“尾递归优化”是如何实现的——“尾递归优化”并不是一个语言实现必须要做的,但这是一个比较有趣的东西,所以我还是想拿来讲一讲。
在前一篇文章中有一个例子:
program recursion
procedure f(integer d);
begin
write(d);
f(d + 1)
end
begin
f(1)
end
这个例子是我用来测试我写的解释器是否正常实现了最基本的尾递归优化的。
这段代码用JAVA翻译过来是这样的:
public class Test {
static void f(long l) {
System.out.println(l);
f(l + 1);
}
public static void main(String[] args) {
f(0);
}
}
就是对一个方法f进行递归调用并且没有任何正常的退出逻辑(要退出就只能抛出异常或我们主动杀死那个进程)。
这段JAVA代码如果运行起来,很快就会发生堆栈溢出的错误(java.lang.StackOverflowError),但我写的SNL代码运行在我实现的SNL解释器中却会一直运行下去,直到那个进程被杀死(不会有类似堆栈溢出的问题抛出来)。之所以会这样就是因为我的解释器实现了“尾递归优化”。
什么是“尾递归优化”呢?
我对尾递归优化的定义是:一个过程返回之前,调用的最后一条语句如果是对当前过程的递归调用时,则可以用一个小技巧把递归调用变成循环。这里使用到的“小技巧”就是尾递归优化了。
科班出身的程序员都受过这样的训练:把各种递归的程序修改成等价的没有递归调用的程序。这其中的各种方法一般都很难实现一个通用的转换程序——输入一个递归的代码,输出一段等价的,无递归的代码——但对于最后一条执行语句是对当前过程的递归时,这个转换过程就比较容易实现了。
从尾递归优化的定义中可以看出两个关键点:
- 先找到最后一条可执行语句(并判断是否是对当前过程的递归)。
- 使用那个“小技巧”。
首先,我们如何知道最后一条语句是哪条呢?
我们知道,SNL语言的语句有七种:条件语句;循环语句;输入语句;输出语句;赋值语句;过程调用语句;return语句
同时,我们知道,SNL文法中,我们解析stmList时,是要先解析第一条stm,然后再看后面的是否还有其它stmList。
这样我们有以下逻辑可以判断当前识别到的stm是否可能是最后一条:
- 如果当前stm之后不再有stmList,则可以认为这个stm就是最后一条了。
- 如果之后再有stmList,且这个stmList下的第一个stm是return语句,则可以认为这个stm就是最后一条了。
有了这两个逻辑还不算完,因为“条件语句”内部可以复合stmList的,这样我就需要在“当前stmList的最后一条语句是条件语句”的情况下还需要判断这个条件语句的两个可能的分支中的stmList的最后一条语句的最后一条语句是哪条了(即然是分支,那么两个分支都自己的最后一条,也就是说可能有多条语句都是最后一条)。
注:对于“循环语句”也是可以在内部复合stmList的,但因为一般我们只有在运行时才知道最后一次循环是什么时候结束的,所以我们暂时不考虑循环语句。
找到最后一条语句(或多条都可能在自己的分支内是最后一条)之后,判断是否为当前过程的递归就简单了:首先它必须是“过程调用语句”,然后其过程签名必须与当前过程完全相同(我在实现SNL语言的解释器时,为了实现简单,直接用过程名做为过程的签名了,但这样实现存在一个问题:在这个SNL语言的实现中就不支持重载了)。
现在我们已经找到了一些可能是最后一条的语句了,那么只要运用那个“小技巧”就可以了。
这个小技巧非常简单:跳转到当前过程的最开头(跳转之前,需要清理一下当前过程创建的上下文——也就是当前过程自己定义的变量等内容)即可。
其实我们自己想像一下也可以想到:递归调用其实就是跳转到最开头,这就是循环了。
到这里还没完——我们如何跳到最开头去呢?
如果我们写的是编译器就比较简单:直接用一个类似JMP或GOTO这样的指令就可以了。
但我们现在写的是解释器,解释器在解释一个proc时几乎不会把整个解释的逻辑放到同一个方法中来实现的(也是几乎不可能做到的),所以当我们当前所在的是用来解析callStm的方法时,如何可以跳转到很多步调用之前的解析procDeclare方法中去呢?
JAVA中只有一种语言机制可以帮助我们做到这点,即:抛异常。
对SNL语言的解释器实现尾递归优化的更多相关文章
- 使用antlr4及java实现snl语言的解释器
对于antlr4的基础使用,请参考我的前一篇文章<用antlr4来实现<按编译原理的思路设计的一个计算器>中的计算器>. 其实我对于antlr4的理解也仅限于那篇文章的范围,但 ...
- 用VC编译lua源码,生成lua语言的解释器和编译器
用VC编译lua源码,生成lua语言的解释器和编译器 1.去网址下载源码 http://www.lua.org/download.html 2.装一个VC++,我用的是VC6.0 3.接下来我们开始编 ...
- 在线C语言编译器/解释器
在线C语言编译器/解释器 本文介绍两个C语言在线解释器/编译器,这些工具可以提高代码片段检测方便的工作效率,并可以保证这些代码的正确性,而且还可以和别人一起编辑/分享之间的代码,这样可以共同分析代码并 ...
- 用C语言写解释器(一)——我们的目标
声明 为提高教学质量,我所在的学院正在筹划编写C语言教材.<用C语言写解释器>系列文章经整理后将收入书中"综合实验"一章.因此该系列的文章主要阅读对象定为刚学完C语言的 ...
- Scala进阶之路-尾递归优化
Scala进阶之路-尾递归优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 递归调用有时候能被转换成循环,这样能节约栈空间.在函数式编程中,这是很重要的,我们通常会使用递归方法来 ...
- .NET 4.6的RyuJIT尾递归优化的Bug
今天看到园子里有一篇新闻稿.NET 4.6的RyuJIT编译器中发现严重的Bug提到,在.Net 4.6的x64程序中默认启用新的JIT程序RyuJIT在处理尾递归指令的时候有一个Bug,导致无法得到 ...
- Python尾递归优化
Python开启尾递归优化 cpython本身不支持尾递归优化, 但是一个牛人想出的解决办法:实现一个 tail_call_optimized 装饰器 #!/usr/bin/env python2.4 ...
- kotlin递归&尾递归优化
递归: 对于递归最经典的应用当然就是阶乘的计算啦,所以下面用kotlin来用递归实现阶乘的计算: 编译运行: 那如果想看100的阶乘是多少呢? 应该是结果数超出了Int的表述范围,那改成Long型再试 ...
- 【Scala】尾递归优化
以递归方式思考 递归通过灵巧的函数定义,告诉计算机做什么.在函数式编程中,随处可见递归思想的运用.下面给出几个递归函数的例子: object RecursiveExample extends App{ ...
随机推荐
- STL算法设计理念 - 函数对象和函数对象当參数和返回值
函数对象: 重载函数调用操作符的类.其对象常称为函数对象(function object),即它们是行为类似函数的对象. 一个类对象,表现出一个函数的特征,就是通过"对象名+(參数列表)&q ...
- OJ(Online Judge)
OJ:它是Online Judge系统的简称,用来在线检测程序源代码的正确性.著名的OJ有RQNOJ.URAL等.国内著名的题库有北京大学题库.浙江大学题库等.国外的题库包括乌拉尔大学.瓦拉杜利德大学 ...
- python中的is判断引用的对象是否一致,==判断值是否相等
python中的is判断引用的对象是否一致,==判断值是否相等 a = 10 b = 20 list = [1,2,3,4,5] print(a in list) print(b not in lis ...
- C++ - 库函数优先级队列(priority_queue)输出最小值 代码
库函数优先级队列(priority_queue)输出最小值 代码 本文地址: http://blog.csdn.net/caroline_wendy 库函数优先级队列(priority_queue)的 ...
- Cracking the Coding Interview 150题(二)
3.栈与队列 3.1 描述如何只用一个数组来实现三个栈. 3.2 请设计一个栈,除pop与push方法,还支持min方法,可返回栈元素中的最小值.pop.push和min三个方法的时间复杂度必须为O( ...
- promise && than
Promise 构造函数接受一个函数作为参数,该函数的2个参数分别是 resolve 和 reject.他们是2个函数,有 JavaScript 引擎提供,不用自己部署. var promise ...
- EXCEL单元格的获取——多例模式
因为Excel的单元格的行列与单元格是一一相应的,行与列组成的是一对联合主键.给定一个单元格行列或者给定一个单元格名称.须要找到相应的单元格:这样就形成了一种映射关系.须要使用单例模式的变式--多例模 ...
- 怎样在win8系统下建立wifi热点
2012年10月26日,微软正式推出Windows 8操作系统,不少用户也都升级到了最新的Win8.大家知道.在Win7系统下,我们非常方便的就在命令提示符下建立了WIFI热点.那么Win8上是 ...
- Lunix
1.Lunix系统登录.重启.关闭 ①.登录 ②.重启 ③.关闭:shutdown [选项][时间][警告信息] -k 向所有用户发出警告信息 -r 关机后立即重启 -h 关机后不重新启动 -f 快速 ...
- HDU 4850 Wow! Such String!(欧拉道路)
HDU 4850 Wow! Such String! 题目链接 题意:求50W内的字符串.要求长度大于等于4的子串,仅仅出现一次 思路:须要推理.考虑4个字母的字符串,一共同拥有26^4种,这些由这些 ...