语法

最近在实现 Berry 的异常处理特性,进过初步的调查后决定使用类似 Python 的 try-except 异常处理模式,为此要引入三个新的关键字:

  • try:表示异常捕获块的开始,位于异常捕获块中的代码抛出的异常将会被捕获,并由 except 语句指定的代码来处理。
  • except:由该关键字构成的语句后跟随一个用于处理指定异常的代码块。
  • raise:该语句用于抛出一个异常。

异常处理的常见写法类似这样:

try
...
raise error
except ErrorName:
...
end

在 Berry 中,raise 语句后允许跟 1 到 2 个表达式,第一个表达式为抛出的异常值,第二个可选参数为额外的参数。except 语句的写法则比较多:

  • excpet Exception1 {, Exception2}::捕获 Exception1Exception2 等异常。
  • excpet Exception1 {, Exception2} as e [, arg]:捕获 Exception1Exception2 等异常。捕获到的异常对象存储在变量 e 中,同时获取一个可选的额外参数 arg
  • except .. as e [, arg]::将捕获的异常对象存入变量 e,任何异常都会被捕获。同时获取一个可选的额外参数 arg
  • except::捕获所有异常。

字节码设计

功能定义

我们新增了 3 个指令用于运行时的异常处理特性支持。这三个指令是:

  • EXBLK:开辟或关闭异常捕获块。所有在异常捕获块中的发生的异常都会被该块捕获,之后会跳转到指定的异常处理代码中。
  • CATCH:检查被捕获的异常是否在给定的捕获异常值列表中,如果是则应执行相应的处理代码。
  • RAISE:抛出异常值及其附加参数。

这里可能要解释一些概念:“异常值”是指用于表示某种异常的值,它可以是一个数、字符串或者是类,在进行异常捕获时,只有抛出的异常值和捕获列表中的异常值存在匹配时才能执行相应的异常处理代码;“异常附加参数”是指在抛出异常值的同时可以额外传递的一个值,通常用来说明异常的详细信息。

指令参数定义

EXBLK

该指令存在两种模式:

EXBLK 0 sBx

在这个模式下,指令的 A 操作数为 0,该模式下 EXBLK 指令会创建一个异常捕获块。如果在该块中捕获到异常,VM 将会恢复到执行该指令时的状态并跳转到地址为 pc + sBx 的位置执行代码。

EXBLK 1 Bx

在该模式下,指令的 A 操作数为 1,该模式将关闭 Bx 个异常捕获块。使用该指令即表明异常捕获块结束。

在没有 breakreturn 一类的跳转语句时,EXBLK 0 sBxEXBLK 1 Bx 大致分别出现在 try 和第一个 except 语句之间(这是一个异常捕获快的范围)。

CATCH

CATCH 指令对应源码中的 except 语句。其指令格式为:

CATCH A B C

CATCH 指令会到由 AA + B - 1 索引的寄存器中查找是否有匹配的异常,如果有,则:

  1. 从栈顶开始拷贝 C 个值到从 A 开始的寄存器中,这些值就是异常值和异常参数(因此最多有两个)。
  2. 跳过下一条指令(通常是一个跳转到下一个 CATCH 块的指令)。

如果匹配不成功,则不会执行上述操作,因此 CATCH 指令后的一条指令将被执行。

RAISE

该指令对应于脚本中的 raise 语句,其指令格式为:

RAISE 0 B
RAISE 1 B C
RAISE 2

第一种模式中,操作数 A 的值为 0,此时会将 B 操作数索引的寄存器中的值作为异常值抛出。第二种模式中,操作数A为 1,此时会将 B 寄存器中的值作为异常值抛出,同时将 C 寄存器中的值作为额外参数抛出。第三种模式下,RAISE 指令会将现有的异常值和额外参数抛出(它们通常由先前的 RAISE 指令产生)。

字节码的使用

现在,我们通过一段简单的代码来说明字节码的生成方式:

try
raise 'my_except', 'test'
except 'my_except' as e, v:
print(e, v)
end

这段代码会生成下面的字节码(假设该代码段在一个函数中):

Line 1   0: EXBLK   0   [4]         ; jump to 4
Line 2 1: RAISE 1 R256 R257 ; R256: 'my_except', R257: 'test'
2: EXBLK 1 1
3: JMP [13] ; jump to 13
Line 3 4: MOVE R0 R256 ; R256: 'my_except'
5: CATCH R0 1 2
6: JMP [12] ; jump to 12
Line 4 7: GETGBL R2 G:14 ; G14: <function: print>
8: MOVE R3 R0
9: MOVE R4 R1
10: CALL R2 2
11: JMP [13] ; jump to 13
12: RAISE 2
Line 5 13: RET 0 R0

其中第 0,1,2,5,12 条指令是为异常处理新增的指令。

  • 第 0 条指令由 try 语句翻译而成,它开辟一个异常处理块并继续向下执行,如果在该块被关闭前发生了异常,VM 状态将回到该条指令并跳转到第 4 条指令。
  • 第 1 条指令由 raise 语句翻译成,它抛出一个异常,异常值和异常参数分别位于 R256 和 R257 中(也就是常量 0 和常量 1)。异常抛出后,VM 将会返回第 0 条 EXBLK 指令并跳转到第 4 条指令。而以下指令不会被执行。
    • 第 2 条用于实现在离开异常处理块时对其的销毁。这里的指令对应于顺序流程中异常处理块的退出,在跳出外层循环或者函数返回时也要使用该指令退出异常处理块(如果嵌套了多级 try 语句则要退出多层异常处理快)。
    • 第 3 条指令用于跳过和该 try 语句配合的所有 except 语句块。代码如果执行到第 2,3 条指令则说明没有发生异常,因此不必进行异常捕获。
  • Line 3 中的 3 行指令由源代码中的 except 行翻译成,首先使用 MOVE 指令将函数常量表中的 'my_except' 字符串加载到寄存器 R0 中,随后 CATCH 指令会进行异常捕获。
    • 从第 5 条的 CATCH 指令参数中可以看出,第 1 个待匹配异常值存储在 R0 中,总共有 1 个待匹配的异常值(也就是 'my_except' 字符串)。该指令还获取 2 个捕获值(由操作数 C 给出),它们分别对应变量 ev
    • 代码运行时,由于这个 excpet 分支能成功捕获到一个 'my_except' 异常值,因此会被执行。异常捕获的整个过程是:
      1. 第 1 条 RAISE 指令抛出异常
      2. VM 状态重置到 第 0 条 EXBLK 处,并跳转到第 4 条指令(由第 0 条指令的 sBx 操作数给出)
      3. 第 4 ~ 5 条指令被执行,后者匹配到 'my_except' 异常,因此将异常值和异常参数存储到变量 ev
      4. 第 5 条指令 CATCH 匹配成功后会跳过下一条指令,因此 VM 接下来执行指令 7,这里对应源代码第 4 行的异常信息打印代码
      5. 执行到第 11 条指令,跳出当前的 except 分支,也就是跳出整个异常捕获语句。异常捕获和处理的流程结束

这里简单说一下异常处理过程中捕获失败时的情况(从第 5 条指令开始):

  • CATCH 指令匹配异常值失败,不会获取异常变量和异常参数,也不跳过下一条指令
  • 第 6 条的 JMP 语句跳转到第 12 条语句
  • 此时分几种情况:
    • 例子中只有一个 excpet 分支,因此第 12 条指令直处接用 RAISE 重新把当前异常抛给上级异常处理机制。
    • 实际上可能存在多个 except 分支,此时第 12 条指令对应下一个分支的开始。在最后一条分支后总会有一个用于捕获失败时重新抛出异常的 RAISE 指令。

总结

到此,字节码层面和源码之间对应的转换关系已经说完。接下来我们需要根据这些关系来设计编译器的相关部分。当然,异常处理机制的实现还离不开运行时的支持,因此我们还要实现这三条字节码的运行时功能。这些内容将在后面的文章中讲解。

Berry 异常处理 1: 语法和字节码设计的更多相关文章

  1. Java虚拟机-字节码指令

    目录 字节码指令 字节码与数据类型 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 字节码指令 Java虚拟机的 ...

  2. AOP AspectJ 字节码 语法 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. Javassist 字节码 语法 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  5. Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码--转载

    在本系列的最后三篇文章中,我展示了如何用 Javassist 框架操作类.这次我将用一种很不同的方法操纵字节码——使用 Apache Byte Code Engineering Library (BC ...

  6. [WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建

    看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的 ...

  7. 通过字节码分析Java异常处理机制

    在上一次[https://www.cnblogs.com/webor2006/p/9691523.html]初步对异常表相关的概念进行了了解,先来回顾一下: 其源代码也贴一下: 下面来看一下jclas ...

  8. 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...

  9. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

随机推荐

  1. 牛客小白月赛18 G Forsaken的三维数点

    思路: 这是一道树状数组和二分的题,用线段树空间直接爆,时间也会超 然后这道题我犯了一个很低级的错误,导致我wa了十发左右,一个int型变量用lld输入,然后他给的提示是运行错误,我哭了,我一直以为是 ...

  2. python 3和python 2 的不同之 f - strings

    python3.6版本及以上版本才能使用 f "{}{}{}" f-string 格式化输出

  3. Electron 常见问题

    导读: 以下记录了作者在实践中遇到的问题和最后的解决方法,如果有错误或者更新更完美的解决方案,欢迎留言指正.交流. 1.jQuery/RequireJS/Meteor/AngularJS 的问题 jQ ...

  4. buuctf zip伪加密

    平时伪加密总是依赖osx,这道题无法直接解压,所以研究一下伪加密先放两张图(图是偷的)一般在压缩源文件数据区全局方式位标记处,真加密为 09 00,伪加密为00 00,而后面将压缩源文件目录区全局方式 ...

  5. 2018-12-2-C#-Span-入门

    title author date CreateTime categories C# Span 入门 lindexi 2018-12-02 11:32:46 +0800 2018-06-18 11:1 ...

  6. KiCAD差分布线

    KiCAD差分布线方法 KiCAD在进行差分布线的时候,会自动按照网路名称生成差分对,所以差分对的名称必须是以_P_N或+/-结束,这样才能找到一对差分对,比如说CAN网络,可以定义为CAN_P/CA ...

  7. Codeforces 19E&BZOJ 4424 Fairy(好题)

    日常自闭(菜鸡qaq).不过开心的是看了题解之后1A了.感觉这道题非常好,必须记录一下,一方面理清下思路,一方面感觉自己还没有完全领会到这道题的精髓先记下来以后回想. 题意:给定 n 个点,m 条边的 ...

  8. NLP(一) Python常用开发工具

    一.Numpy NumPy系统是Python的一种开源的数值计算包. 包括: 1.一个强大的N维数组对象Array: 2.比较成熟的(广播)函数 库: 3.用于整合C/C++和Fortran代码的工具 ...

  9. BZOJ 3252: 攻略(思路题)

    传送门 解题思路 比较好想的一道思路题,结果有个地方没开\(long\) \(long\) \(wa\)了三次..其实就是模仿一下树链剖分,重新定义重儿子,一个点的重儿子为所有儿子中到叶节点权值最大的 ...

  10. HDU-4825 Xor Sum(字典树求异或最大值)

    题目链接:点此 我的github地址:点此 Problem Description Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整 ...