用Python编写一个简单的Lisp解释器的教程老酱

大多数计算机语言都有许多语法规约(例如关键字、中缀操作符、括号、操作符优先级、点标记、分号等等),但是,作为Lisp语言家族中的一员,Scheme所有的语法都是基于包含在括号中的、采用前缀表示的列表的。这种表示看起来似乎有些陌生,但是它具有简单一致的优点。(一些人戏称”Lisp”是”LotsofIrritatingSillyParentheses“――“大量恼人、愚蠢的括号“――的缩写;我认为它是”LispIsSyntacticallyPure“――“Lisp语法纯粹”的缩写。)考虑下面这个例子:

/*Java*/if(x.val()>0){z=f(a*x.val()+b);}/*Scheme*/(if(>(valx)0)(set!z(f(+(*a(valx))b))))注意上面的惊叹号在Scheme中并不是一个特殊字符;它只是”set!“这个名字的一部分。(在Scheme中)只有括号是特殊字符。类似于(set!xy)这样以特殊关键字开头的列表在Scheme中被称为一个特殊形式(specialform);Scheme的优美之处就在于我们只需要六种特殊形式,以及另外的三种语法构造――变量、常量和过程调用。

在该表中,var必须是一个符号――一个类似于x或者square这样的标识符――number必须是一个整型或者浮点型数字,其余用斜体标识的单词可以是任何表达式。exp…表示exp的0次或者多次重复。

更多关于Scheme的内容,可以参考一些优秀的书籍(如[Friedman和Fellesein

**语言解释器的职责**一个语言解释器包括两部分:

2、执行:程序的内部表示(由解释器)根据语言的语义规则进行进一步处理,进而执行源程序的实际运算。(Lispy的)执行部分由eval函数实现(注意,该函数覆盖了Python内建的同名函数)。

下面的图片描述了解释器的解释流程,(图片后的)交互会话展示了parse和eval函数对一个小程序的操作方式:

这里,我们采用了一种尽可能简单的内部表示,其中Scheme的列表、数字和符号分别使用Python的列表、数字和字符串来表示。

执行:eval下面是eval函数的定义。对于上面表中列出的九种情况,每一种都有一至三行代码,eval函数的定义只需要这九种情况:

defeval(x,env=global_env):"Evaluateanexpressioninanenvironment."ifisa(x,Symbol):#variablereferencereturnenv.find(x)[x]elifnotisa(x,list):#constantliteralreturnxelifx[0]=='quote':#(quoteexp)(_,exp)=xreturnexpelifx[0]=='if':#(iftestconseqalt)(_,test,conseq,alt)=xreturneval((conseqifeval(test,env)elsealt),env)elifx[0]=='set!':#(set!varexp)(_,var,exp)=xenv.find(var)[var]=eval(exp,env)elifx[0]=='define':#(definevarexp)(_,var,exp)=xenv[var]=eval(exp,env)elifx[0]=='lambda':#(lambda(var*)exp)(_,vars,exp)=xreturnlambda*args:eval(exp,Env(vars,args,env))elifx[0]=='begin':#(beginexp*)forexpinx[1:]:val=eval(exp,env)returnvalelse:#(procexp*)exps=[eval(exp,env)forexpinx]proc=exps.pop(0)returnproc(*exps)isa=isinstanceSymbol=streval函数的定义就是这么多…当然,除了environments。Environments(环境)只是从符号到符号所代表的值的映射而已。一个新的符号/值绑定由一个define语句或者一个过程定义(lambda表达式)添加。

让我们通过一个例子来观察定义然后调用一个Scheme过程的时候所发生的事情(lis.py>提示符表示我们正在与Lisp解释器进行交互,而不是Python):

lis.py>(definearea(lambda(r)(*3.141592653(*rr))))lis.py>(area3)28.274333877当我们对(lambda(r)(*3.141592653(*rr)))进行求值时,我们在eval函数中执行elifx[0]=='lambda'分支,将(_,vars,exp)三个变量分别赋值为列表x的对应元素(如果x的长度不是3,就抛出一个错误)。然后,我们创建一个新的过程,当该过程被调用的时候,将会对表达式['',3.141592653['','r','r']]进行求值,该求值过程的环境(environment)是通过将过程的形式参数(该例中只有一个参数,r)绑定为过程调用时所提供的实际参数,外加当前环境中所有不在参数列表(例如,变量*)的变量组成的。新创建的过程被赋值给global_env中的area变量。

那么,当我们对(area3)求值的时候发生了什么呢?因为area并不是任何表示特殊形式的符号之一,它必定是一个过程调用(eval函数的最后一个else:分支),因此整个表达式列表都将会被求值,每次求值其中的一个。对area进行求值将会获得我们刚刚创建的过程;对3进行求值所得的结果就是3。然后我们(根据eval函数的最后一行)使用参数列表[3]来调用这个新创建的过程。也就是说,对exp(也就是['',3.141592653['','r','r']])进行求值,并且求值所在的环境中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=outerdeffind(self,var):"FindtheinnermostEnvwherevarappears."returnselfifvarinselfelseself.outer.find(var)注意Env是dict的一个子类,也就是说,通常的字典操作也适用于Env类。除此之外,该类还有两个方法,构造函数__init__和find函数,后者用来为一个变量查找正确的环境。理解这个类的关键(以及我们需要一个类,而不是仅仅使用dict的根本原因)在于外部环境(outerenvironment)这个概念。考虑下面这个程序:

(definemake-account(lambda(balance)(lambda(amt)(begin(set!balance(+balanceamt))balance))))(definea1(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,operatorasopenv.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':lambdax:x[1:],'append':op.add,'list':lambda*x:list(x),'list':lambdax:isa(x,list),'null':lambdax:x==[],'symbol':lambdax:isa(x,Symbol)})returnenvglobal_env=add_globals(Env())PS1:对麦克斯韦方程组的一种评价是“一般地,宇宙间任何的电磁现象,皆可由此方程组解释”。AlanKay所要表达的,大致就是Lisp语言使用自身定义自身(Lispwas“definedintermsofLisp”)这种自底向上的设计对软件设计而言具有普遍的参考价值。――译者注

解析(Parsing):read和parse

接下来是parse函数。解析通常分成两个部分:词法分析和语法分析。前者将输入字符串分解成一系列的词法单元(token);后者将词法单元组织成一种中间表示。Lispy支持的词法单元包括括号、符号(如set!或者x)以及数字(如2)。它的工作形式如下:

>>>program="(set!x*2(*x2))">>>tokenize(program)['(','set!','x*2','(','*','x','2',')',')']>>>parse(program)['set!','x*2',['*','x',2]]有许多工具可以进行词法分析(例如MikeLesk和EricSchmidt的lex)。但是我们将会使用一个非常简单的工具:Python的str.split。我们只是在(源程序中)括号的两边添加空格,然后调用str.split来获得一个词法单元的列表。

2将会在编译时被认定是错误。另一方面,Java和Python并不需要在编译时检测出表达式x/0是一个错误,因此,如你所见,一个错误应该何时被识别并没有严格的规定。Lispy使用read函数来实现parse函数,前者用以读取任何的表达式(数字、符号或者嵌套列表)。

tokenize函数获取一系列词法单元,read通过在这些词法单元上调用read_from函数来进行工作。给定一个词法单元的列表,我们首先查看第一个词法单元;如果它是一个')',那么这是一个语法错误。如果它是一个'(‘,那么我们开始构建一个表达式列表,直到我们读取一个匹配的')'。所有其它的(词法单元)必须是符号或者数字,它们自身构成了一个完整的列表。剩下的需要注意的就是要了解'2‘代表一个整数,2.0代表一个浮点数,而x代表一个符号。我们将区分这些情况的工作交给Python去完成:对于每一个不是括号也不是引用(quote)的词法单元,我们首先尝试将它解释为一个int,然后尝试float,最后尝试将它解释为一个符号。根据这些规则,我们得到了如下程序:

defread(s):"ReadaSchemeexpressionfromastring."returnread_from(tokenize(s))parse=readdeftokenize(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')'returnLelif')'==token:raiseSyntaxError('unexpected)')else:returnatom(token)defatom(token):"Numbersbecomenumbers;everyothertokenisasymbol."try:returnint(token)exceptValueError:try:returnfloat(token)exceptValueError: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='lis.py>'):"Aprompt-read-eval-printloop."whileTrue:val=eval(parse(raw_input(prompt)))ifvalisnotNone:printto_string(val)下面是函数工作的一个例子:

>>>repl()lis.py>(definearea(lambda(r)(*3.141592653(*rr))))lis.py>(area3)28.274333877lis.py>(definefact(lambda(n)(if(<=n1)1(*n(fact(-n1))))))lis.py>(fact10)3628800lis.py>(fact100)93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000lis.py>(area(fact10))4.1369087198e+13lis.py>(definefirstcar)lis.py>(definerestcdr)lis.py>(definecount(lambda(itemL)(ifL(+(equalitem(firstL))(countitem(restL)))0)))lis.py>(count0(list012300))3lis.py>(count(quotethe)(quote(themorethemerrierthebiggerthebetter)))4Lispy有多小、多快、多完备、多优秀?

我们使用如下几个标准来评价Lispy:

bash$grep"^\s*[^#\s]"lis.py|wc903983423*高效:Lispy计算(fact100)只需要0.004秒。对我来说,这已经足够快了(虽然相比起其它的计算方式来说要慢很多)。

*优秀:这一点需要读者自己确定。我觉得,相对于我解释Lisp解释器这一目标而言,它已经足够优秀。

真实的故事

了解解释器的工作方式会很有帮助,有一个故事可以支持这一观点。1984年的时候,我在撰写我的博士论文。当时还没有LaTeX和MicrosoftWord――我们使用的是troff。遗憾的是,troff中没有针对符号标签的前向引用机制:我想要能够撰写“正如我们将要在@theoremx页面看到的”,随后在合适的位置撰写”@(settheoremx\n%)”(troff寄存器\n%保存了页号)。我的同伴,研究生TonyDeRose也有着同样的需求,我们一起实现了一个简单的Lisp程序,使用这个程序作为一个预处理器来解决我们的问题。然而,事实证明,当时我们用的Lisp善于读取Lisp表达式,但在采用一次一个字符的方式读取非Lisp表达式时效率过低,以至于我们的这个程序很难使用。

在这个问题上,Tony和我持有不同的观点。他认为(实现)表达式的解释器是困难的部分;他需要Lisp为他解决这一问题,但是他知道如何编写一个短小的C过程来处理非Lisp字符,并知道如何将其链接进Lisp程序。我不知道如何进行这种链接,但是我认为为这种简单的语言编写一个解释器(其所具有的只是设置变量、获取变量值和字符串连接)很容易,于是我使用C语言编写了一个解释器。因此,戏剧性的是,Tony编写了一个Lisp程序,因为他是一个C程序员;我编写了一个C程序,因为我是一个Lisp程序员。

THE END
1.EXPECTTHEUNEXPECTED–CambridgeEnglishDictionaryThe show's producers are not offering any details of the new season, except to say viewers should expect the unexpected. Everyone involved in emergency drills knows to expect the unexpected. When we started at the academy, they told us to expect the unexpected. My best advice to travellers https://dictionary.cambridge.org/da/ordbog/engelsk/expect-the-unexpected
2.易错英语翻译30句经管文库(原现金交易版)经易错英语翻译30句_学习方法网 --- 今天小编为大家精心整理了一篇有关易错英语翻译30句的相关内容,https://bbs.pinggu.org/forum.php?mod=viewthread&tid=13193031
3.今日词句1.disposeof#流利英语##英语能为投资人打开另一个世界#今1.dispose of 处理,解决;清除,办妥。 2.sacrifice n. 牺牲,舍弃。 3.sb be honoured (to do)荣幸 4.detect vt. 发现,查明,侦查出。 5.refreshment n. 点心,食物和饮料。 6.magistrate n.地方执法官 7.cottager n. 村民 8.negligence n.失误 https://xueqiu.com/6693531173/317471574
4.2025年6月大学英语四级翻译常考词组复习(13)四级take (make) a stand for 捍卫 take (make) a stand against 反对 come after 跟随 in support of 支持 lie up 躺着休息 以上是新东方在线英语四级频道小编为大家带来的“2025年6月大学英语四级翻译常考词组复习(13)”,希望考生们都能取得出色的成绩。https://cet4.koolearn.com/20241218/911490.html
5.argumentoutofrangeexception发生在程序运行时导致应用崩溃的致命最新消息:某知名软件公司近日因其应用程序频繁出现“ArgumentOutOfRangeException”错误,导致用户数据丢失,引发广泛关注和讨论。该事件不仅影响了公司的声誉,也让人们对软件开发中的异常处理问题产生了深思。 异常的根源与影响 “ArgumentOutOfRangeException”是一种常见的运行时错误,通常发生在程序试图访问超出集合或数组边界的http://dlx.jingcancan.vip/jccgl/7313.html
6.每日一句考研英语长难句Day314【翻译】 最后,理解这句了么? 【整句翻译】一系列的事故,包括2007年冷却塔的部分倒塌以及一个地下管道系统被发现泄漏,引发了人们对佛蒙特扬基核电厂的安全以及Entergy公司的管理的严重质疑,尤其是该公司对问题管道做出了误导性声明之后更是如此。https://mp.weixin.qq.com/s?__biz=MzA3MDM0MjUzMA==&mid=2651146742&idx=5&sn=0d0487ad5d228c5f71035c28ff4498e4&chksm=8532372f8330b40440f974c71947f9e8ab450f4df5e331cae8d78c620e97179b523e35bb0c74&scene=27
7.SyntaxError:unexpectedEOFwhileparsing的翻译是SyntaxError: unexpected EOF while parsing 选择语言:从中文简体中文翻译英语日语韩语俄语德语法语阿拉伯文西班牙语葡萄牙语意大利语荷兰语瑞典语希腊语捷克语丹麦语匈牙利语希伯来语波斯语挪威语乌尔都语罗马尼亚语土耳其语波兰语到中文简体中文翻译英语日语韩语俄语德语法语阿拉伯文西班牙语葡萄牙语意大利语荷兰语瑞典语希腊语http://xiongyaliyu.zaixian-fanyi.com/fan_yi_4090789
8.syntaxerror:感谢您的访问,如果您是这里的新手,请考虑订阅我们的时事通讯。 在下一篇文章中见。 再见! 翻译自:https://www.thecrazyprogrammer.com/2020/05/solve-syntaxerror-unexpected-eof-while-parsing.html syntaxerror:https://blog.csdn.net/culing2941/article/details/108616994
9.Python3SyntaxError:unexpectedEOFwhileparsingPython3 SyntaxError: unexpected EOF while parsing,程序员大本营,技术文章内容聚合第一站。https://www.pianshen.com/article/7995167680/
10.pythonSyntaxError:解析时出现意外的EOFfor i in range(0,24): File "<ipython-input-29-db3022a769d1>", line 1 for i in range(0,24): ^ SyntaxError: unexpected EOF while parsing File "<ipython-input-25-df0a44131c36>", line 1 for k in range(n_hours_advance,n_hours_advance+n_hours_window): ^ SyntaxError: unexpectedhttps://segmentfault.com/q/1010000042523163/a-1020000042523167
11.svn错误以及中文翻译学习分享进步"Unexpected EOF on stream" msgstr "流意外结束" msgid "Malformed stream data" msgstr "非法流数据" msgid "Unrecognized stream data" msgstr "无法识别的流数据" msgid "Unknown svn_node_kind" msgstr "未知的 svn_node_kind" msgid "Unexpected node kind found" msgstr "发现意外节点种类" msgid "Can'https://www.iteye.com/blog/col-man-2151916
12.{%trans"Thisisthetitle."%}strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */ + { + Py_XDECREF (exc); + Py_XDECREF (val); + Py_XDECREF (trb); + prompt = ps2; + } + else /* some other syntax error */ + { + PyErr_Restore (exc, val, trb); + PyErr_Print (); + free (code); + https://github.com/tlmdsg/pythondocument/commit/0045f2c2922e8405ae167284f39ab1344ca4db57.diff
13.linuxeolPython常见代码错误汇总与解决思路-吐血经验前言一、常见的“不熟悉”错误syntaxerror: invalid syntax翻译:处理:syntaxerror: unexpected EOF while parsing翻译:处理:syntaxerror: invalid character in identifier翻译:处理:indentationerror: expected an i python报错EOL https://blog.51cto.com/topic/linux-eol.html
14.用Python编写一个简单的Lisp解释器的教程pythonraiseSyntaxError('unexpected EOF while reading') token=tokens.pop(0) if'('==token: L=[] whiletokens[0] !=')': L.append(read_from(tokens)) tokens.pop(0)# pop off ')' returnL elif')'==token: raiseSyntaxError('unexpected )') https://www.jb51.net/article/63406.htm
15.EffectiveGo译版Go语言中没有do或while循环,只有一个略微泛化的for循环;switch语句更加灵活;if和switch语句接受一个像for循环一样的可选初始化语句;break和continue语句可以带有可选标签,用于标识需要中断或继续的位置;此外,还引入了包括类型switch和多路通信复用器select在内的新的控制结构。语法上也略有不同:不需要使用括号,而且https://www.jianshu.com/p/a27dd9a315b7