如何使用Python编写一个Lisp解释器Word文档下载推荐.docx

上传人:b****4 文档编号:16944453 上传时间:2022-11-27 格式:DOCX 页数:12 大小:28.76KB
下载 相关 举报
如何使用Python编写一个Lisp解释器Word文档下载推荐.docx_第1页
第1页 / 共12页
如何使用Python编写一个Lisp解释器Word文档下载推荐.docx_第2页
第2页 / 共12页
如何使用Python编写一个Lisp解释器Word文档下载推荐.docx_第3页
第3页 / 共12页
如何使用Python编写一个Lisp解释器Word文档下载推荐.docx_第4页
第4页 / 共12页
如何使用Python编写一个Lisp解释器Word文档下载推荐.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

如何使用Python编写一个Lisp解释器Word文档下载推荐.docx

《如何使用Python编写一个Lisp解释器Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《如何使用Python编写一个Lisp解释器Word文档下载推荐.docx(12页珍藏版)》请在冰豆网上搜索。

如何使用Python编写一个Lisp解释器Word文档下载推荐.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1