`
cloverprince
  • 浏览: 127164 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PLisp: 集成在Python中的LISP语言实现 (1)

阅读更多
看了一点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表达式的处理而已。

根据目前定义的函数,这个“语言”功能还非常简单;但是,下篇将引入更多函数,使得这个“语言”逐渐趋近于功能完备的程序设计语言。

分享到:
评论

相关推荐

    hy:嵌入在Python中的Lisp方言

    Hy是嵌入在Python中的Lisp方言。 由于Hy将其Lisp代码转换为Python抽象语法树(AST)对象,因此您可以以Lisp形式轻松掌握Python的整个美丽世界。 要安装Hy的最新稳定版本,只需使用命令pip3 install --user hy 。 ...

    lispy:Python 中的实验性 Lisp 实现

    Lispy 是 Python 中的 Lisp 实现。 它受到 John McCarthy 的论文“符号表达式的递归函数及其机器计算,第一部分”的影响,可在找到; 通用 Lisp; Emacs Lisp 和 Clojure。 一些代码基于的 。 特征 完整功能列表...

    Python-Python受LISP启发的函数式编程思想

    Python受LISP启发的函数式编程思想

    学习用项目,用 Python 实现一个仿 lisp 语言的解释器.zip

    学习用项目,用 Python 实现一个仿 lisp 语言的解释器.zip学习用项目,用 Python 实现一个仿 lisp 语言的解释器.zip学习用项目,用 Python 实现一个仿 lisp 语言的解释器.zip学习用项目,用 Python 实现一个仿 lisp ...

    语言程序设计资料:LISP语言教程.doc

    语言程序设计资料:LISP语言教程.doc

    Lispy:Python编写的类似Lisp的语言

    用Python编写的类似Lisp的语言 文档: : 依存关系 Python 3.8以上 安装 下载最新版本或主要版本(不稳定) 使用python .\Lispy.py <file>.lpy [--debug]启动任何lispy文件,或使用python .\Lispy.py [--debug]启动...

    pLisp:别说话,说话!

    pLisp是Lisp的集成开发环境。 虽然它旨在成为面向初学者的友好的Lisp开发系统,但其功能集已经足够全面,可以满足中小型Lisp项目的需求。 pLisp支持哪些功能? 基本运算符,如CAR,CDR和其他语言原语 pLisp本身...

    hy, 在 python 中,嵌入了一个Lisp方言.zip

    hy, 在 python 中,嵌入了一个Lisp方言 Lisp和 python 应该彼此相爱。 让我们来吧。试试它。 Hylarious攻击Django + Lisppython sh 乐趣中的 miniKanren好,那么,为什么?我们非常棒,但是

    Paradigms.of.Artificial.Intelligence.Programming in.Common.Lisp.pdf

    本书介绍了使用原始的lisp语言来实现机器学习和深度学习的一些实录,采用函数式编程的方式。函数式语言对于不基于统计式的机器学习放法有无与伦比的优势

    lisp语言用Delaunay三角网法求土石方量

    本程序为lisp语言编写的AutoCAD二次开发程序,包括lisp源程序代码和程序测试图。 本程序适合AutoCAD二次开发初学者学习之用,代码比较简单,所用函数也都是基本的函数,并且程序中包含详细注释。适合大学测绘、采矿...

    burgled-batteries, python 和Lisp之间的桥梁( FFI绑定,等等 ).zip

    burgled-batteries, python 和Lisp之间的桥梁( FFI绑定,等等 ) burgled电池:通用 lisp/python 桥burgled电池提供 python ( 具体来说,CPython的python 实现) 和普通Lisp之间的shim 。概要(asdf:load-system "bur

    利用VisualLisp语言实现CAD中轴线任意点的中边桩计算.pdf

    利用VisualLisp语言实现CAD中轴线任意点的中边桩计算.pdf

    Land of Lisp、Machine Learning in Action

    Land of Lisp、Machine Learning in Action

    基于Python实现一个仿lisp语言的解释器.zip

    学习用项目,用Python实现一个仿lisp语言的解释器 支持: 算术表达式 条件表达式 变量 函数 递归 自然算数表达式 (2017/05/23 新增)

    cad lisp 初级教程

    AutoLISP是一种针对扩充及自订AutoCAD函数机能而产生,以LISP为基础的程序设计语言.LISP本身于50年代末期出现,是一种擅于处理串行文字(List of Processing),属第四代人工智能(Artificial Intelligence)的计算机...

    cl-python:Common Lisp中的Python实现

    CLPython-Common Lisp中Python的实现CLPython是用Common Lisp编写的Python的开源实现。 使用CLPython,您可以在Lisp环境中运行Python程序。 用Lisp编写的库可用于Python代码,并且Python库可通过Lisp代码访问。 ...

    python-elisp:适用于Python的Emacs Lisp解析器

    一个简单的软件包,用于从Python解析Emacs Lisp表达式。 简单用法 >> > import elisp >> > numbers = elisp . loads ( "(1 2 3)" ) >> > numbers . car 1 >> > numbers . cdr . cdr . car 3 >> > numbers . cdr . ...

    《Visual Lisp开发人员手册》

    AutoLISP是由Autodesk公司开发的一种LISP程序语言,LISP是List Processor的缩写。通过autolisp编程,可以节省工程师...AutoLISP语言作为嵌入在AutoCAD内部的具有智能特点的编程语言,是开发应用AutoCAD不可缺少的工具。

Global site tag (gtag.js) - Google Analytics