当前位置: 首页 > news >正文

简单程序语言理论与编译技术·19 实现一个解释器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。

LECTURE 19(实现一个解释器)

一、Evaluation

1、Evaluation是继续简化抽象语法树(AST)的过程,直到它只是一个值(value)。换句话说,它是语言动态语义的实现。Evaluator使表达式一次采取一个step来简化自己,经过多个step后,表达式最终将成为一个值。我们称这种策略为small-step或single-step,因为我们每次只走一步。

2、这是简单数学表达式的BNF语法:

我们据此建立以下步骤规则(--> 表示单步):

(1)常量:常量(在本例即整数)已经是值,因此我们不能再对它们进行step。

(2)二元运算符:二元运算符形如e1 bop e2 ,有两个子表达式e1和e2。由于e1和e2也可以是值,我们需要考虑三种不同的情况:

注意,我们建立这些规则是基于e1和e2是steppable的,这对于操作 + * / 是正确的。反之,如果不是这种情况,那么e1或e2应该是一个值,并被早期规则consume掉。这里的规则 I 代表原始操作处理,这与 II 和 III 略有不同,表明我们不仅需要语法,还需要语义来进行一些“计算”。

有趣的是,e1 bop v2 --> e1' bop v2 表示当我们对整个表达式进行step时,我们实际上是递归地对e1进行step。而我们不考虑得e1 bop v2 --> e1' bop v2,实际上包含在 III 中。

3、现在,让我们在自己的解释器中实现single-step。首先,我们需要一个函数来检查一个表达式是否已经是一个值,因为我们不能对值进行step。或者我们也可以说,这个函数是用于检查一个表达式是否是steppable的:

4、接下来,让我们实现small-step的规则。由于我们需要递归地对表达式进行步骤,我们需要一个递归函数。此外,鉴于规则 II 和 III 只需要语法,而规则 I 还需要语义。因此,我们需要一个特殊的step函数:

5、最后,我们需要用一个eval和一个interp函数将它们连接起来。

至此,我们已经完成了一个用于数学表达式的小解释器。不过这里,由于我们只有整数,所以到目前为止我们不需要类型检查和类型推断。

6、不过,你可能注意到我们一直只提及single-step或small-step,那么有没有big-step呢?有的兄弟,有的。让我们先给一个定义:

请注意,两种step都有其重要性:小步语义在建模复杂语言特性时往往更容易处理,而大步语义则更类似于解释器的实际实现方式。

7、现在,让我们为大步建立规则:

(1)常量:常量big-step到它们自己:i ==> i

(2)二元运算符:二元运算符只big-step其两个子表达式,然后应用原始运算符:e1 bop e2 ==> v 如果 e1 ==> v1 和 e2 ==> v2 并且 v 是原始操作 v1 bop v2 的结果。

8、接下来,我们可以实现函数eval_big和eval_bop ,前者进行大步Evaluation,而后者进行与语义相关的Binop的big-step。当然,它们使用一个interp_big函数来相互连接:

二、SimPL Interpreter

1、作为一个例子,我们将使用一种非常简单的编程语言,即SimPL它的BNF语法如下图。你可以在https://github.com/AugustineYang/OCaml-Interpreter中找到SimPL Interpreter – Dev-Ready,这是一个可以用于学习补充的半成品代码。Interpreter是解释器的意思。

为了简单起见,我们只考虑SimPL支持以下三种结构:

(1)值:整数、布尔值和标识符(由英文字母组成)。

(2)二元运算符:+(加)、*(乘)和 <=(小于等于)。

(3)语句:let 语句和 if 语句。

在实践中,我们需要补充其代码,让其至少能识别、分析诸如 let x = 3110 in x + x和 if x <= 3 then true else false的语句。

2、你为 if 表达式设计的小步规则应该是这样的:

注意,规则 III 表明 e1 是steppable的。如果不是,那么它应该是 true 或 false,并且应该已经被规则 I 或规则 II consume掉。下图是应该在小步函数中添加的内容,你也可以在这里增加代码以进行类型检查:

你需要在函数 is_value 中添加一些检查,以便处理布尔值和 if 表达式:

此外,我们还需要在函数 step_binop 中为 “ <= ” 操作符添加一个step规则:

3、你为 if 表达式设计的大步规则应该是这样的:

下图是应该在大步函数中添加的内容。如果你愿意,也可以在这里进行类型检查。此外,也不要忘记布尔值和" <= "的处理:

 到目前为止,我们的Evaluator已经能够解释没有变量的简单 if 表达式了,其完整代码可在之前给的github仓库中找到。但当我们来看 let 表达式,发现我们必须处理变量的问题。这,就涉及到了一个新的问题,替换(Substitution)。

三、Substitution

1、Substitution是用相应的值或计算形式替换变量或表达式的过程。这是处理变量绑定、函数应用或宏扩展的基本机制。我们需要引入一个新的符号 e' { e / x } ,意思是在表达式 e' 中用 e 来替换 x。直观上,在 x 出现在 e' 的任何地方,我们都应该用 e 来替换 x。例如:

2、现在,我们可以为 let 表达式制定small-step规则:

请记住,如果规则 II 中的 e1 不是一个steppable的表达式,那么 let 表达式应该已经被规则 I consume掉。这就是我们可以安全地step e1 的原因。上述规则还说明了一个事实,x 作为一个变量,不是一个值,并且也是unsteppable的。如果我们试图step一个变量而不是对其进行替代,这意味着该变量是未绑定的(即,之前没有被适当的替代消耗),在这种情况下应该引发错误。就像这段简单的 OCaml 代码一样:

当然了,这实际上是一个类型检查错误。我们可以暂时不考虑错误类型,只在Evaluator尝试step一个变量时,这里引发错误。

此外,我们也可以为 let 表达式制定一些big-step规则:

3、现在我们已经通过 let 表达式在 SimPL 中引入了替换。让我们看看应该如何在 let x = v1 in e2 > e2 { v1 / x } 中处理 e2 { v1 / x } 。换言之,我们可以给出 SimPL 表达式的替换的仔细定义了:

(1)常量:整数和布尔值中没有出现变量,因此替换不会改变它们:

(2)变量:要么我们遇到相同的变量 x,这意味着我们应该进行替换,要么我们遇到其他不同名称的变量,比如 y,简单地,在这种情况下我们就不应该进行替换:

(3)二元运算符:我们应该在子表达式内部,递归地进行替换:

(4) if 表达式:我们应该像对待二元运算符一样在内部递归:

(5) let 表达式:对于这种情况,假设我们有两个更复杂的情况:

这里还有一些额外的例子,帮助你更好地理解在 let 表达式上的替换:

四、Lambda Calculus Interpreter

1、考虑一个语法如下图:

眼熟吗?这个语法正是 lambda 计算。它只有三种表达式:变量、函数应用和匿名函数,其中唯一的值是匿名函数。虽然这个语法甚至没有类型,但它可以表达任何可计算的函数。现在,让我们在我们的解释器中添加对 lambda 计算语法的支持。

2、有几种方法可以为 lambda 演算定义Evaluation语义。下图是一个与OCaml非常接近的规则:

记得吗?这是之前笔记中涵盖的按值调用(call-by-value)语义,它要求在应用函数之前将参数reduce为值。当然,我们也可以尝试定义按名调用(call-by-name)的语义,在按名调用语义中,e2 不必reduce到值。如果 e2 的值从未被需要,这可以带来很大的效率:

3、现在我们需要为 lambda 演算定义替换操作。我们可以做一个适用于按名称调用或按值调用的定义。受到 SimPL 替换的启发,制定这些规则应该没有太大困难。以下是我们定义的开始:

那么函数呢?在 SimPL 中,当我们遇到同名的有界变量时停止替换;否则,我们继续。在 lambda 演算中,或许我们也可以这样做:

4、不过,这真的正确吗?让我们考虑下图这两个表达式,它们都应该忽略参数并返回 x(即,分配给 x 的值):

我们可以看到,表达式 I 忽略参数并返回 z,它绑定到 x,结果是正确的。而表达式 II 变成了一个标识函数,该函数返回传来的值,而不是 x 的值,显然,结果出了点问题。

这里,我们需要重新考虑第二个例子。这一次,我们用不同的颜色给不同的 z 上色:

我们发现蓝色的 z 消失了,这正是错误的源头。实际上,这种 z 的消失被称为捕获。破损的替换捕获了 z,并使其与 z 成为同一个变量。它破坏了变量的作用域。这就是替换未能完成其工作的原因。因此,我们需要增强我们的替换以避免捕获的发生:

相关文章:

  • HTTP新的二进制格式与多路复用
  • 股指期货四个品种合约是什么意思?
  • OpenCV基础——图像滤波和形态学操作
  • 数字化计算机语言特性对比
  • 力扣HOT100之矩阵:48. 旋转图像
  • 《JVM考古现场(十四):混沌重启——从量子永生到宇宙热寂的终极编译》
  • 「Unity3D」TMP_InputField关闭虚拟键盘后,再次打开虚拟键盘,此时无法回调onSelect的问题
  • 文章配图新纪元:OpenAI新推出的GPT-4o原生图像生成功能启示
  • Joint Receiver Design for Integrated Sensing and Communications
  • 双向链表的理解
  • 【Kettle安装】Kettle安装过程, 电脑已安装java23,安装Kettle 出现报错:尝试启动 Java 虚拟机(JVM)时失败解决方法
  • JavaEE-MyBatis概述第一个程序
  • Redis GEO
  • [7-02-02].第15节:生产经验 - 消费者相关操作
  • 农产品直卖平台的设计与实现(代码+数据库+LW)
  • Burpsuite 伪造 IP
  • 数据结构与算法:二维动态规划
  • 图解AUTOSAR_SWS_CANDriver
  • 【区块链安全 | 第二十四篇】单位和全局可用变量(二)
  • 翻译: 人工智能如何让世界变得更美好四
  • 做电商一件代发的网站/学生个人网页设计作品
  • 做班级相册网站的目的意义/培训心得体会万能模板
  • 衡水大型网站建设/软件推广方案经典范文
  • 网站开发成本包括/免费推广网站排行榜
  • 佛山网站建设哪家效果好/百度指数有哪些功能
  • 网站的服务器每年都要续费的吗/百度关键词优化方法