对于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解释器中却会一直运行下去,直到那个进程被杀死(不会有类似堆栈溢出的问题抛出来)。之所以会这样就是因为我的解释器实现了“尾递归优化”。

什么是“尾递归优化”呢?

我对尾递归优化的定义是:一个过程返回之前,调用的最后一条语句如果是对当前过程的递归调用时,则可以用一个小技巧把递归调用变成循环。这里使用到的“小技巧”就是尾递归优化了。

科班出身的程序员都受过这样的训练:把各种递归的程序修改成等价的没有递归调用的程序。这其中的各种方法一般都很难实现一个通用的转换程序——输入一个递归的代码,输出一段等价的,无递归的代码——但对于最后一条执行语句是对当前过程的递归时,这个转换过程就比较容易实现了。

从尾递归优化的定义中可以看出两个关键点:

  1. 先找到最后一条可执行语句(并判断是否是对当前过程的递归)。
  2. 使用那个“小技巧”。

首先,我们如何知道最后一条语句是哪条呢?

我们知道,SNL语言的语句有七种:条件语句;循环语句;输入语句;输出语句;赋值语句;过程调用语句;return语句

同时,我们知道,SNL文法中,我们解析stmList时,是要先解析第一条stm,然后再看后面的是否还有其它stmList。

这样我们有以下逻辑可以判断当前识别到的stm是否可能是最后一条:

  1. 如果当前stm之后不再有stmList,则可以认为这个stm就是最后一条了。
  2. 如果之后再有stmList,且这个stmList下的第一个stm是return语句,则可以认为这个stm就是最后一条了。

有了这两个逻辑还不算完,因为“条件语句”内部可以复合stmList的,这样我就需要在“当前stmList的最后一条语句是条件语句”的情况下还需要判断这个条件语句的两个可能的分支中的stmList的最后一条语句的最后一条语句是哪条了(即然是分支,那么两个分支都自己的最后一条,也就是说可能有多条语句都是最后一条)。

注:对于“循环语句”也是可以在内部复合stmList的,但因为一般我们只有在运行时才知道最后一次循环是什么时候结束的,所以我们暂时不考虑循环语句。

找到最后一条语句(或多条都可能在自己的分支内是最后一条)之后,判断是否为当前过程的递归就简单了:首先它必须是“过程调用语句”,然后其过程签名必须与当前过程完全相同(我在实现SNL语言的解释器时,为了实现简单,直接用过程名做为过程的签名了,但这样实现存在一个问题:在这个SNL语言的实现中就不支持重载了)。

现在我们已经找到了一些可能是最后一条的语句了,那么只要运用那个“小技巧”就可以了。

这个小技巧非常简单:跳转到当前过程的最开头(跳转之前,需要清理一下当前过程创建的上下文——也就是当前过程自己定义的变量等内容)即可。

其实我们自己想像一下也可以想到:递归调用其实就是跳转到最开头,这就是循环了。

到这里还没完——我们如何跳到最开头去呢?

如果我们写的是编译器就比较简单:直接用一个类似JMP或GOTO这样的指令就可以了。

但我们现在写的是解释器,解释器在解释一个proc时几乎不会把整个解释的逻辑放到同一个方法中来实现的(也是几乎不可能做到的),所以当我们当前所在的是用来解析callStm的方法时,如何可以跳转到很多步调用之前的解析procDeclare方法中去呢?

JAVA中只有一种语言机制可以帮助我们做到这点,即:抛异常。

对SNL语言的解释器实现尾递归优化的更多相关文章

  1. 使用antlr4及java实现snl语言的解释器

    对于antlr4的基础使用,请参考我的前一篇文章<用antlr4来实现<按编译原理的思路设计的一个计算器>中的计算器>. 其实我对于antlr4的理解也仅限于那篇文章的范围,但 ...

  2. 用VC编译lua源码,生成lua语言的解释器和编译器

    用VC编译lua源码,生成lua语言的解释器和编译器 1.去网址下载源码 http://www.lua.org/download.html 2.装一个VC++,我用的是VC6.0 3.接下来我们开始编 ...

  3. 在线C语言编译器/解释器

    在线C语言编译器/解释器 本文介绍两个C语言在线解释器/编译器,这些工具可以提高代码片段检测方便的工作效率,并可以保证这些代码的正确性,而且还可以和别人一起编辑/分享之间的代码,这样可以共同分析代码并 ...

  4. 用C语言写解释器(一)——我们的目标

    声明 为提高教学质量,我所在的学院正在筹划编写C语言教材.<用C语言写解释器>系列文章经整理后将收入书中"综合实验"一章.因此该系列的文章主要阅读对象定为刚学完C语言的 ...

  5. Scala进阶之路-尾递归优化

    Scala进阶之路-尾递归优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 递归调用有时候能被转换成循环,这样能节约栈空间.在函数式编程中,这是很重要的,我们通常会使用递归方法来 ...

  6. .NET 4.6的RyuJIT尾递归优化的Bug

    今天看到园子里有一篇新闻稿.NET 4.6的RyuJIT编译器中发现严重的Bug提到,在.Net 4.6的x64程序中默认启用新的JIT程序RyuJIT在处理尾递归指令的时候有一个Bug,导致无法得到 ...

  7. Python尾递归优化

    Python开启尾递归优化 cpython本身不支持尾递归优化, 但是一个牛人想出的解决办法:实现一个 tail_call_optimized 装饰器 #!/usr/bin/env python2.4 ...

  8. kotlin递归&尾递归优化

    递归: 对于递归最经典的应用当然就是阶乘的计算啦,所以下面用kotlin来用递归实现阶乘的计算: 编译运行: 那如果想看100的阶乘是多少呢? 应该是结果数超出了Int的表述范围,那改成Long型再试 ...

  9. 【Scala】尾递归优化

    以递归方式思考 递归通过灵巧的函数定义,告诉计算机做什么.在函数式编程中,随处可见递归思想的运用.下面给出几个递归函数的例子: object RecursiveExample extends App{ ...

随机推荐

  1. HDOJ 4259 Double Dealing

    找每一位的循环节.求lcm Double Dealing Time Limit: 50000/20000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  2. Erlang 又生虫了

    好久不玩Erlang了.近期想鼓捣Eresye,下了个最新版OTP 17,结果.发现了问题. 安装这个最新版的Erlang (erl 6.0)后,用erlc编译了Eresye 1.2.5,并放入其li ...

  3. andorid自己定义ViewPager之——子ViewPager滑到边缘后直接滑动父ViewPager

    近期的项目中,有一个需求要用ViewPager中嵌套ViewPager去实现整个效果.没做不论什么处理做出来后,仅仅能不停的滑动子ViewPager,父ViewPager就无法滑动了,这样肯定是不满足 ...

  4. cocos2dX 之数据存储

    今天我们来看cocos2dX里面的数据存储类, CCUserDefault, 如今的游戏基本都会把用户信息保存下来, 以便于再次进入游戏的时候读取, 为了方便起见,有时我们也能够用CCUserDefa ...

  5. ogg 传输进程启动报错 Missing filename opening checkpoint file.

    GGSCI (hosta) 48> view report dpfull ************************************************************ ...

  6. TypeError: db.addUser is not a function : @(shell):1:1 ——mongoDB创建新用户名密码的方法

    不多说,旧版本使用 db.addUser("root","root") 新版本使用这句会出现这个错误提示 TypeError: db.addUser is no ...

  7. [计算机故障处理]EXCEL文件双击不能直接打开

    同事的电脑里的EXCEL文件不知什么原因双击不能直接打开了,双击只能打开软件而且是没有任何表格的,但通过软件中的“打开”再找到指定的文件能打开. 解决方案: 打开excel,依次选择:工具-选项-常规 ...

  8. java获取本周 上周的所有日期

    1 根据当前日期获得所在周的日期区间(周一和周日日期) public String getTimeInterval(Date date) { Calendar cal = Calendar.getIn ...

  9. Opencv+Zbar二维码识别(二维码校正)

    二维码和车牌识别基本都会涉及到图像的校正,主要是形变和倾斜角度的校正,一种二维码的畸变如下图: 这个码用微信扫了一下,识别不出来,但是用Zbar还是可以准确识别的~~. 这里介绍一种二维码校正方法,通 ...

  10. 5 Application 对象

    5.1鸟瞰Application对象 5.2 必须了解的面向显示特性 5.2.1 使用ScreenUpdating改进和完善执行性能 代码清单5.1:实现屏幕更新的性能 '代码清单5.1: 实现屏幕更 ...