设为首页 收藏本站
查看: 765|回复: 0

[经验分享] PLisp: 集成在Python中的LISP语言实现 (1)

[复制链接]

尚未签到

发表于 2017-5-3 07:47:59 | 显示全部楼层 |阅读模式
看了一点LISP的书,猛然觉得,LISP的原理很简单。LISP里面只有1种数据结构:Symbolic Expression,简称sexp。程序本身也是sexp,处理的数据也是sexp。也就是,如果你能解释用sexp写成的程序,你就制成了LISP解释器。
LISP里面,sexp分为两种:表和原子。list(表),内部有序地包含0个或多个sexp。除此之外,其他东西都是atom(原子),比如symbol(符号,类似“标识符”), 数字,字符串,布尔值等。例外是nil,既是表(空表)又是原子。
LISP的执行,就是sexp的求值(evaluation)过程。求值规则如下:
- 非符号的原子,解释为其本身。
- 符号,解释为其在符号表中对应的值。符号表是一个“符号->值”的对应表。
- 表,将其第一个元素求值,得到一个函数,将其他元素求值,作为这个函数的参数。然后调用函数,返回值作为这个表的值。(如果函数是特殊函数,则参数先不求值,即“按名传递”;一般函数,参数先求值,即“按值传递”)

下面,我要利用Python实现一种LISP方言。
我不想写解析器(parser),因为Python本身已经提供了足够的数据结构。Python中有list数据结构(数组),类似LISP中的表。Python字符串看成symbol也可以。其他的数据类型可以都作为普通的原子。
于是,我们可以用Python表达式:

["+", ["*", 3, 4], ['*', 5, 6]]

表示LISP的表达式:

(+ (* 3 4) (5 6))


又如,Python表达式:

["let",
[['x', 2],
['y', 3]],
['*', x, y]]

表示LISP的表达式:

(let
((x 2)
(y 3))
(* x y))


解释的过程,就可以写成一个Python函数,输入一个Python表达式,输出另一个Python表达式。中间可以用副作用来处理IO。

如何实现呢?
首先,求值需要一个符号表,这个任何语言都一样,记录变量(或者LISP的符号)对应的值。而LISP的符号表会随着求值的过程(副作用)而改变。

class Context(UserDict):
def __init__(self, parent=None):
UserDict.__init__(self)
self.parent = parent

Context类表示一个符号表,或者说LISP求值的“上下文”。Context只不过是一个dict而已,输入符号,对应到值。这个parent稍候解释,因为context可以嵌套。
然后,每个LISP函数都可以用Python函数表示。定义为由“上下文和参数的笛卡尔积”到python值的映射。简而言之,每个LISP函数对应的Python函数,除了“普通参数”以外,还要Context作为额外的参数。比如:

def add(context, one, another):
return one + another
# ['+', 1, 2] calls this function and evaluates to 3

这个函数和符号表无关,不读取符号表,也不改变符号表。
又如:

def _set(context, key, value):
context[key] = value
return value
# ['set', 'x', 40] calls this function and sets symbol 'x' to 40

这是一个“赋值函数”,通过修改符号表,将符号key的对应于value的值。


def _print(context, expr):
print expr
return expr
# ['print', ['quote', 'hello world!']] calls this function and prints "hello world!" to standard output

这个完全依赖求值的副作用,打印输出。
函数的基本形式就是这样。
基本的LISP执行环境需要几个基本的LISP函数。最基本的当然是eval函数了。LISP中,eval的功能是将一个表达式求值。我定义Python函数_eval,为了避免和内置函数eval命名冲突。

def _eval(context, expr): # 输入expr,求expr的值
if isinstance(expr, str): # 对于符号,查找符号表获得值
cur_context = context
# 因为符号表可以嵌套,所以迭代查找。
while cur_context is not None:
if expr in cur_context:
return cur_context[expr]
else:
cur_context = cur_context.parent
raise KeyError(expr)
elif isinstance(expr, list): # 对于表,转换成函数调用。
first, rest = expr[0], expr[1:]
func = _eval(context, first) # 递归对第一个元素求值,得到函数。
if getattr(func,"call_by_name",False)==False: # 处理特殊函数。
evrest = [_eval(context, e) for e in rest]
else:
evrest = rest
return func(context, *evrest) # 调用函数,得到返回值
else:
return expr # 对于其他原子,返回其本身。

正好分3个分支,处理了LISP求值的3种情况。
上述代码提到了“特殊函数”。一般的函数,调用前,要将参数求值,再传入。如:

(+ (- 9 3) 4)

必须先求出(- 9 3)的值:6,才能传入"+"函数。
但是,另外一些函数需要“按名传递”,即根据某些条件,选择性地求值。如:

(if (eq (+ 1 1) 2) right wrong)

if函数是条件函数。先求第一个参数的值,如果是真,则求第二个参数的值,第三个参数不求值;如果是假,求第三个参数的值,第二个参数不动。
因此,对于这些特殊的LISP函数,需要在Python函数上做一些标记。我的做法是,在定义函数后,给这些函数设置其call_by_name的值为True。或者用@decorator更简单。
典型的if函数,定义如下:

@call_by_name
def _if(context, condition, iftrue, iffalse=None):
if _eval(context, condition):
return _eval(context, iftrue)
else:
return _eval(context, iffalse)

@call_by_name内部完成call_by_name=True的赋值工作。而函数体内部,先用_eval函数求第一个参数的值,对于真假两种情况,分别调用两个分支。
另一个典型的“按名传递”的函数是quote,这个函数避免其内部的符号被求值。LISP中:

(quote abc)
'abc

两者求值都得到符号abc。由于quote如此常用,因此LISP中有特殊的引号'语法,方便quote函数的应用。
在Python中:

@call_by_name
def quote(context, expr):
return expr
def q(expr):
return ['quote',expr]

quote函数不对expr求值,而直接返回expr。我还定义了Python函数q,类似LISP的',方便书写。
有了以上基本函数,其实可以编一些简单的程序了。以下是一个演示。

# 首先,使用之前,要实例化一个Context对象:
default_context = Context()
# 然后,将基本函数加入这个符号表
default_context["eval"] = _eval
default_context["print"] = _print
default_context["quote"] = quote
default_context["set"] = set
default_context["+"] = add
# 最后,用eval函数求值即可。
# 这是Hello world
_eval(default_context, ["print", q("Hello world!")])
# 屏幕上显示Hello world!
# 赋值语句
_eval(default_context, ["set", q("x"), 5])
# 改变符号表,"x"对应整数5。
# 计算简单的加法
result = _eval(default_context, ["+", ["+", 1, "x"], 3])
print result
# 输出9


总结:
通过以上程序,可以看出,这种实现,输入是完全合法的Python表达式,输出也是Python表达式。求值仅仅是用Python语言做了Python表达式的处理而已。
根据目前定义的函数,这个“语言”功能还非常简单;但是,下篇将引入更多函数,使得这个“语言”逐渐趋近于功能完备的程序设计语言。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-372276-1-1.html 上篇帖子: python调用Java-JPype使用介绍(一)(转) 下篇帖子: Dave Python 练习十五 -- 面向对象编程
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表