如何使用Python编写一个Lisp解释器Word文档下载推荐.docx
《如何使用Python编写一个Lisp解释器Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《如何使用Python编写一个Lisp解释器Word文档下载推荐.docx(12页珍藏版)》请在冰豆网上搜索。
![如何使用Python编写一个Lisp解释器Word文档下载推荐.docx](https://file1.bdocx.com/fileroot1/2022-11/27/2fc794b8-6816-4878-8425-fbd169a7842a/2fc794b8-6816-4878-8425-fbd169a7842a1.gif)
Scheme的优美之处就在于我们只需要六种特殊形式,以及另外的三种语法构造——变量、常量和过程调用:
形式(Form)
语法
语义和示例
常量引用
var
一个符号,被解释为一个变量名;
其值就是这个变量的值。
示例:
x
常量字面值
number
数字的求值结果为器本身。
12
或者
-3.45e+6
引用
(quote
exp)
返回exp的字面值;
不对它进行求值
(quote(abc))⇒(abc)
条件测试
(if
testconseqalt)
对test进行求值;
如果结果为真,那么对conseq进行求值并返回结果;
否则对alt进行求值并返回结果。
(if(<
1020)(+11)(+33))⇒2
赋值
varexp)
对exp进行求值并将结果赋值给
var,
var必须已经进行过定义(使用define或者作为一个封闭过程的参数)。
x2(*xx))
定义
(define
var
在最内层环境(environment)中定义一个新的变量并将对exp表达式求值所得的结果赋给该变量。
(definer3)
(definesquare(lambda(x)(*xx))).
过程
(lambda(var...)
创建一个过程,其参数名字为var...,过程体为相应的表达式。
Example:
(lambda(r)(*3.141592653(*rr)))
(表达式)序列
(begin
exp...)
按从左到右的顺序对表达式进行求值,并返回最终的结果。
(begin(set!
x1)(set!
x(+x1))(*x2))⇒4
过程调用
(procexp...)
如果proc是除了
if,set!
define,lambda,begin,或者quote之外的其它符号的话,那么它会被视作一个过程。
它的求值规则如下:
所有的表达式exp都将被求值,然后这些求值结果作为过程的实际参数来调用该相应的过程。
(square12)⇒144
在该表中,var必须是一个符号——一个类似于x或者square这样的标识符——number必须是一个整型或者浮点型数字,其余用斜体标识的单词可以是任何表达式。
exp...表示exp的0次或者多次重复。
更多关于Scheme的内容,可以参考一些优秀的书籍(如
Friedman和Fellesein,
Dybvig,
Queinnec,
Harvey和Wright或者
Sussman和Abelson)、视频(Abelson和Sussman)、教程(Dorai,
PLT,或者
Neller)、或者
参考手册。
语言解释器的职责
一个语言解释器包括两部分:
1.解析:
解析部分接受一个使用字符序列表示的输入程序,根据语言的语法规则
对输入程序进行验证,然后将程序翻译成一种中间表示。
在一个简单的解释器中,中间表示是一种树结构,紧密地反映了源程序中语句或表达式的嵌套结构。
在一种称为编译器的语言翻译器中,内部表示是一系列可以直接由计算机(作者的原意是想说运行时系统——译者注)执行的指令。
正如SteveYegge所说,“如果你不明白编译器的工作方式,那么你不会明白计算机的工作方式。
“
Yegge介绍了编译器可以解决的8种问题(或者解释器,又或者采用Yegge的典型的反讽式的解决方案)。
Lispy的解析器由parse函数实现。
2.执行:
程序的内部表示(由解释器)根据语言的语义规则进行进一步处理,进而执行源程序的实际运算。
(Lispy的)执行部分由eval函数实现(注意,该函数覆盖了Python内建的同名函数)。
下面的图片描述了解释器的解释流程,(图片后的)交互会话展示了parse和eval函数对一个小程序的操作方式:
>
program="
(begin(definer3)(*3.141592653(*rr)))"
parse(program)
['
begin'
['
define'
'
r'
3],['
*'
3.141592653,['
]]]
eval(parse(program))
28.274333877
这里,我们采用了一种尽可能简单的内部表示,其中Scheme的列表、数字和符号分别使用Python的列表、数字和字符串来表示。
执行:
eval
下面是eval函数的定义。
对于上面表中列出的九种情况,每一种都有一至三行代码,eval函数的定义只需要这九种情况:
defeval(x,env=global_env):
"
Evaluateanexpressioninanenvironment."
ifisa(x,Symbol):
#variablereference
returnenv.find(x)[x]
elifnotisa(x,list):
#constantliteral
returnx
elifx[0]=='
quote'
:
#(quoteexp)
(_,exp)=x
returnexp
if'
#(iftestconseqalt)
(_,test,conseq,alt)=x
returneval((conseqifeval(test,env)elsealt),env)
'
#(set!
varexp)
(_,var,exp)=x
env.find(var)[var]=eval(exp,env)
#(definevarexp)
env[var]=eval(exp,env)
lambda'
#(lambda(var*)exp)
(_,vars,exp)=x
returnlambda*args:
eval(exp,Env(vars,args,env))
#(beginexp*)
forexpinx[1:
]:
val=eval(exp,env)
returnval
else:
#(procexp*)
exps=[eval(exp,env)forexpinx]
proc=exps.pop(0)
returnproc(*exps)
isa=isinstance
Symbol=str
eval函数的定义就是这么多...当然,除了environments。
Environments(环境)只是从符号到符号所代表的值的映射而已。
一个新的符号/值绑定由一个define语句或者一个过程定义(lambda表达式)添加。
让我们通过一个例子来观察定义然后调用一个Scheme过程的时候所发生的事情(lis.py>
提示符表示我们正在与Lisp解释器进行交互,而不是Python):
lis.py>
(definearea(lambda(r)(*3.141592653(*rr))))
(area3)
当我们对(lambda(r)(*3.141592653(*rr)))进行求值时,我们在eval函数中执行elifx[0]=='
分支,将
(_,vars,exp)三个变量分别赋值为列x的对应元素(如果
x的长度不是3,就抛出一个错误)。
然后,我们创建一个新的过程,当该过程被调用的时候,将会对表达式['
3.141592653['
'
]]
进行求值,求值过程的环境(environment)是通过将过程的形式参数(该例中只有一个参数,
r)绑定为过程调用时所提供的实际参数,外加当前环境中所有不在参数列表(例如,变量*)的变量组成的。
新创建的过程被赋值给global_env中的
area变量。
那么,当我们对(area3)求值的时候发生了什么呢?
因为
area并不是任何表示特殊形式的符号之一,它必定是一个过程调用(eval函数的最后一个else:
分支),因此整个表达式列表都将会被求值,每次求值其中的一个。
对area进行求值将会获得我们刚刚创建的过程;
对3进行求值所得的结果就是3。
然后我们(根据eval函数的最后一行)使用参数列表[3]来调用这个新创建的过程。
也就是说,对exp(也就是
]])进行求值,并且求值所在的环境中r是3,并且外部环境是全局环境,因此*
是乘法过程。
现在,我们可以解释一下Env类的细节了:
classEnv(dict):
Anenvironment:
adictof{'
var'
val}pairs,withanouterEnv."
def__init__(self,parms=(),args=(),outer=None):
self.update(zip(parms,args))
self.outer=outer
deffind(self,var):
FindtheinnermostEnvwherevarappears."
returnselfifvarinselfelseself.outer.find(var)
注意Env是dict的一个子类,也就是说,通常的字典操作也适用于
Env类。
除此之外,该类还有两个方法,构造函数__init__和
find
函数,后者用来为一个变量查找正确的环境。
理解这个类的关键(以及我们需要一个类,而不是仅仅使用dict)的根本原因)在于外部
环境(outer
environment)这个概念。
考虑下面这个程序:
make-account
(lambda(balance)
(lambda(amt)
balance(+balanceamt))
balance))))
a1
(make-account100.00))
(a1-20.00)
每个矩形框都代表了一个环境,并且矩形框的颜色与环境中最新定义的变量的颜色相对应。
在程序的最后两行我们定义了a1并且调用了(a1-20.00);
这表示创建一个开户金额为100美元的银行账户,然后是取款20美元。
在对(a1-20.00)求值的过程中,我们将会对黄色高亮表达式进行求值,该表达式中具有三个变量。
amt
可以在最内层(绿色)环境中直接找到。
但是balance
在该环境中没有定义:
我们需要查看绿色环境的外层环境,也就是蓝色环境。
最后,+代表的变量在这两个环境中都没有定义;
我们需要进一步查看外层环境,也就是全局(红色)环境。
先查找内层环境,然后依次查找外部的环境,我们把这一过程称之为词法定界(lexicalscoping)。
Procedure.find负责根据词法定界规则查找正确的环境。
剩下的就是要定义全局环境。
该环境需要包含+过程以及所有其它Scheme的内置过程。
我们并不打算实现所有的内置过程,但是,通过导入Python的math模块,我们可以获得一部分这些过程,然后我们可以显式地添加20种常用的过程:
defadd_globals(env):
AddsomeSchemestandardprocedurestoanenvironment."
importmath,operatorasop
env.update(vars(math))#sin,sqrt,...
env.update(
{'
+'
op.add,'
-'
op.sub,'
op.mul,'
/'
op.div,'
not'
op.not_,
'
op.gt,'
<
op.lt,'
='
op.ge,'
op.le,'
op.eq,
equal?
op.eq,'
eq?
op.is_,'
length'
len,'
cons'
lambdax,y:
[x]+y,
car'
lambdax:
x[0],'
cdr'
x[1:
],'
append'
op.add,
list'
lambda*x:
list(x),'
list?
isa(x,list),
null?
x==[],'
symbol?
isa(x,Symbol)})
returnenv
global_env=add_globals(Env())
解析(Parsing):
read和parse
接下来是parse函数。
解析通常分成两个部分:
词法分析和语法分析。
前者将输入字符串分解成一系列的词法单元(tokens),后者将词法单元组织成一种中间表示。
Lispy支持的词法单元包括括号、符号(如set!
或者x),以及数字(如2)。
它的工作形式如下:
x*2(*x2))"
tokenize(program)
('
x*2'
x'
2'
)'
]
2]]
有许多工具可以进行词法分析(例如MikeLesk和EricSchmidt的lex)。
但是我们将会使用一个非常简单的工具:
Python的str.split。
我们只是在(源程序中)括号的两边添加空格,然后调用str.split来获得一个词法单元的列表。
接下来是语法分析。
我们已经看到,Lisp的语法很简单。
但是,一些Lisp解释器允许接受表示列表的任何字符串作为一个程序,从而使得语法分析的工作更加简单。
换句话说,字符串(set!
12)可以被接受为是一个语法上有效的程序,只有当执行的时候解释器才会抱怨set!
的第一个参数应该是一个符号,而不是数字。
在Java或者Python中,与之等价的语句1=2将会在编译时被认定是错误。
另一方面,Java和Python并不需要在编译时检测出表达式x/0是一个错误,因此,如你所见,一个错误应该何时被识别并没有严格的规定。
Lispy使用read函数来实现parse函数,,前者用以读取任何的表达式(数字、符号或者嵌套列表)。
tokenize函数获取一系列词法单元,read通过在这些词法单元上调用
read_from函数来进行工作。
给定一个词法单元的列表,我们首先查看第一个词法单元;
如果它是一个'
,那么这是一个语法错误。
,那么我们开始构建一个表达式列表,直到我们读取一个匹配的'
。
所有其它的(词法单元)必须是符号或者数字,它们自身构成了一个完整的列表。
剩下的需要注意的就是要了解'
代表一个整数,
2.0代表一个浮点数,而x代表一个符号。
我们将区分这些情况的工作交给Python去完成:
对于每一个不是括号也不是引用(quote)的词法单元,我们首先尝试将它解释为一个int,然后尝试float,最后尝试将它解释为一个符号。
根据这些规则,我们得到了如下程序:
defread(s):
ReadaSchemeexpressionfromastring."
returnread_from(tokenize(s))
parse=read
deftokenize(s):
Convertastringintoalistoftokens."
returns.replace('
('
).replace('
)'
).split()
defread_from(tokens):
Readanexpressionfromasequenceoftokens."
iflen(tokens)==0:
raiseSyntaxError('
unexpectedEOFwhilereading'
)
token=tokens.pop(0)
if'
==token:
L=[]
whiletokens[0]!
L.append(read_from(tokens))
tokens.pop(0)#popoff'
returnL
elif'
unexpected)'
returnatom(token)
defatom(token):
Numbersbecomenumbers;
everyothertokenisasymbol."
try:
returnint(token)
exceptValueError:
returnfloat(token)
returnSymbol(token)
最后,我们将要添加一个函数to_string,用来将一个表达式重新转换成Lisp可读的字符串;
以及一个函数repl,该函数表示read-eval-print-loop(读取-求值-打印循环),用以构成一个交互式的Lisp解释器:
defto_string(exp):
ConvertaPythonobjectbackintoaLisp-readablestring."
return'
.join(map(to_string,exp))+'
ifisa(exp,list)elsestr(exp)
defrepl(prompt='
):
Aprompt-read-eval-printloop."
whileTrue:
val=eval(parse(raw_input(prompt)))
ifvalisnotNone:
printto_string(val)
下面是函数工作的一个例子:
repl()
(definefact(lambda(n)(if(<
=n1)1(*n(fact(-n1))))))
(fact10)
3628800
(fact100)
9332621544394415268169923885626670049071596826438162146859296389521759999322991
5608941463976156518286253697920827223758251185210916864000000000000000000000000
(area(fact10))
4.1369087198e+13
(definefirstcar)
(definerestcdr)
(definecount(lambda(itemL)(ifL(+(equal?
item(firstL))(countitem(restL)))0)))
(count0(list012300))
3
(count(quotethe)(quote(themorethemerrierthebiggerthebetter)))
4
Lispy有多小、多快、多完备、多优秀?
我们使用如下几个标准来评价Lispy:
∙小巧:
Lispy