使用pyparsing 实现一个let语言的解释器

let语言来自eopl 《Essentials of Programming Languages》,本段只实现第一个简单的let proc的语言。

eopl的书中的代码使用scheme编写,可以在github中取得。https://github.com/mwand/eopl3  就是作者之一写的,本文将第三章的一个例子转为python语言来实现。

本文的2个py代码在: https://github.com/wangdxh/eopl3-in-python   let文件夹下面

python 安装pyparsing  : pip install pyparsing

let语言的定义如下:

let xx = 0, bb = 24 in

if zero? ( xx )  then -(bb,3)  else - (xx , 5 )

使用pyparsing解析得到:

[['let', [['xx', '=', [0]], ['bb', '=', [24]]], 'in', ['if', ['zero?', ['xx']], 'then', ['-', ['bb'], [3]], 'else', ['-', ['xx'], [5] ]]]]

let语言的pyparsing语法定义如下:

使用pyparsing 实现一个let语言的解释器_第1张图片

identifier = Word(alphas, alphanums+'_-?')  字符开头,包含字符,数字,下划线,减号,问号

number = Combine( Optional('-') + Word(nums)).setParseAction(lambda t:int(t[0]))  数字:整数

var_exp = Group( identifier )变量表达式,

const_exp = Group( number) 整数表达式

使用pyparsing的Group将变量和整数都封装在一个list里面,这样变量,整数,和其他的语法一样,解析出来的结果都是list,便于解释时整体处理。

expression = Forward()  表达式,先定义为Forword类型,用于递归的类型定义,先给出一个类型声明,后面给出具体的定义。

LPAREN, RPAREN, COMMA = map(Suppress, '(),')  左括号,右括号,逗号定义为Superss,这样在引用到LPAREN等变量的时候,这些符号并不出现在解析结果中,但是如果是‘(’去构建表达式的时候,还是会出现在结果中,Supress(‘,’) 则不会。

diff_exp = Group( '-' + LPAREN + expression + COMMA + expression + RPAREN)

减号语法 - (xx, 3),解析为: ['-', ['xx'], [3]] 这里的3是整数,并不是字符串,因为在number的定义中,设置了解析操作 setParseAction(lambda t:int(t[0])) 讲解析结果转换成了int。

这里注意express, 减法的定义里面包含了experssion,expression的定义里面会包含减法,let语法,if语法等多个语法,会造成递归引用,所以要用Forword

zero_exp = Group( Keyword('zero?') + LPAREN + expression + RPAREN)

判断是否为bool类型,zero?(xx) 解析为:['zero?', ['xx']]

if_exp = Group( Keyword('if') + expression + Keyword('then') + expression + Keyword('else') + expression)

if zero? ( xx )  then -(bb,3)else - (xx , 5 )  if语句定义 解析为:

['if', ['zero?', ['xx']], 'then', ['-', ['bb'], [3]], 'else', ['-', ['xx'], [5]]]  每一个expression 都解析为一个列表,除了整数外第一个字段都是表达式的名称。

let_exp = Group(Keyword('let') + Group( delimitedList( Group( identifier + '=' + expression ))) + "in" +  expression)

let 语法,后面跟着一个变量定义的列表,‘in’关键字,最后的expression是执行的body,里面可以引用let后面定义的变量。 delimitedList 表示为 a + ','+ a + ',' + a 至少有一个a,后面跟则多个以逗号间隔的a,用于多个变量声明的列表。

proc_exp = Group(Keyword('proc') + LPAREN + identifier + RPAREN + expression)

定义函数的方式,let f = proc (x) -(x,1) in (f 30), 以proc开头,括号里面是定义的函数参数,目前只支持一个参数的定义,参数后面跟着一个expression,是函数体。在定义的let 和proc里面执行体的表达式暂时都只有一个,方便解析

call_exp = Group('(' + expression + expression + ')' )

调用表达式,以()开始和结尾,这里的解析结果中()是存在的,并且list的第一个字段就是 '(',  因为它没有用Supress封装。第一个expression是函数定义或者函数变量,第二个expression是函数执行的参数

let f = proc (x) -(x,1) in (f 30) 解析为:

[  ['let', [['f', '=', ['proc', 'x', ['-', ['x'], [1]]]]], 'in', ['(', ['f'], [30], ')']]  ]

接下来定义

expression << (diff_exp | zero_exp | if_exp | call_exp | proc_exp | let_exp | const_exp | var_exp)  << 是pyparsing的语法,表示真正地定义expression。 expression定义为前面定义的表达式中的任何一个。所以各个表达式和expression是递归地引用的。

现在一个完整的语法分析的定义就完成了,调用 expression.parseString(program)对字符串进行解析,expression的分析结果是ParseResults,这个类可以当作list来进行操作,而且所以的分析结果都是放在一个大的list里面的,我们的每条expression定义的时候都用Group封装了,所以每个expression又会独立地在有个list内。

解释执行:

let f = proc (x) -(x,1) in (f 30) 解析为:

[ ['let', [['f', '=', ['proc', 'x', ['-', ['x'], [1]]]]], 'in', ['(', ['f'], [30], ')']]  ]

def value_of_program(program):

return value_of(expression.parseString(program)[0], init_env()) parseString解析结果的第一个list字段,就是我们的expression的解析结果,这个结果也是一个list,第一个字段,就是各个表达式的第一个字段,整数的第一个字段,就解析为整数,变量的第一个字段就是变量的字符串,整数和变量所在的list也只有一个字段。

我们引入了一个环境变量的概念,用户扩展 let语法中定义的 变量声明,当有let xx = 0, bb=1 的时候,会向环境中扩展一个xx 和 bb的定义,然后去执行let中body的定义,body中如果有引用到xx或者bb,就去环境中查找它的值,或者为整数,或者为一个函数。 环境的扩展时完成闭包的一个重要的环节,这里环境是按照变量的定义动态扩展的。当用let将函数定义为变量的时候,解释执行时会生成一个闭包的python函数,这个函数会和 变量绑定,扩展到环境中。

因为有些let语法中的函数的字符在python中是不支持的,比如?,-,所以我们使用一个dict去对应 let语法中的 某个要执行的expression的名称和 python中的某个实际函数。使用修饰符定义,在文件enviroments.py中定义:函数dict,这个symboletofun修饰符将修饰符后面带着的字符串参数和要修饰的函数体,在funcdict中进行dict对应。

使用pyparsing 实现一个let语言的解释器_第2张图片

在pylet.py中调用valueof 执行expression表达式的时候:根据expression的表达式的解析结果list的第一个字段,判断如果字段类型是int,说明他是一个number常量,它的valueof值就是 int自身;字段的其他类型就是字符串了,‘let’,‘if’,‘(’,‘zero?’ , 'xx',等等,根据它的字符串去函数列表中去查找对应的处理函数,每一个expression我们都有一个对应的函数处理,如果找不到,说明这个字符串是一个变量定义,那么变量定义的valueof 就是去环境中查找变量名称对应的值,或者为int值,或者为python的函数值。查找环境中的符号对应的值,是apply_env,扩展环境中的符号对应值是extend_env函数。

使用pyparsing 实现一个let语言的解释器_第3张图片

首先来看 let的valueof

使用pyparsing 实现一个let语言的解释器_第4张图片

将 let的解析结果list,赋值给4个变量,name就是‘let’,然后遍历变量声明list,将变量对应的表达式的值计算之后,和 变量的符号串如,‘xx’  增加到环境中,extend_env会在原来环境的基础上创建一个新的环境,并不影响原来的环境值,只有在执行本次let的body时,使用新定义的变量环境,退出本次let的环境之后,原来的环境中并不包含本次let中的变量声明。而且环境也使用list实现,当出现变量名称重复之后,会使用最后插入的变量值。 let语法的执行结果就是它的body的执行结果,body执行时,环境中增加了let语法中声明的变量的信息。

减号,zero?,if的执行都比较类似,如下,

使用pyparsing 实现一个let语言的解释器_第5张图片

接下来是非常重要的函数定义和调用了

函数定义如下 :当碰到proc时,对这条expression的解释,会生成一个python的函数体的值,这样对于函数调用的(),或者 变量的环境扩展都很方便。proc过程会通过proceduer的函数,将其body和env闭包地保存,返回一个只带函数参数的新的python函数体,这个函数体里面用到时创建proc 时的body 和 环境的值。例如 let f = proc (x) -(x,1) in (f 30) 执行时,proc_func 会为proc生成一个 函数体,就是procedure返回的python函数,然后let的变量声明表达式执行时,会将这个 python函数和 ‘f’,对应 扩展到环境中,在in后面的 (f,30),‘f’的valueof 执行时,会查找到当时保留的python函数体。

使用pyparsing 实现一个let语言的解释器_第6张图片

函数的调用,(f,30)或者(( proc(x) -(x,1)  30)),第一个expression的valueof 必然是一个python函数体,然后将第二个expression的值计算出来,作为参数,执行。

使用pyparsing 实现一个let语言的解释器_第7张图片

测试执行:

执行这些字符串查看结果:

''' let xx = 0, bb = 24 in if zero? ( xx ) then -(bb,3) else - (xx , 5 ) '''

let f = proc (x) -(x,1) in (f 30)

(proc(f)(f 30)  proc(x)-(x,1))

((proc (x) proc (y) -(x,y)  5) 6)

let f = proc(x) proc (y) -(x,y) in ((f -(10,5)) 6)

最后来一个eopl代码中的例子执行:

使用pyparsing 实现一个let语言的解释器_第8张图片

zero? 的解释执行竟然计算反了,没有加not,已更新。

作者:小王

你可能感兴趣的:(使用pyparsing 实现一个let语言的解释器)