`
cloverprince
  • 浏览: 130174 次
  • 性别: 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表达式的处理而已。

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

分享到:
评论

相关推荐

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

    - **递归与循环**:Lisp 语言中的常见控制流结构,以及如何在 Python 中实现它们。 - **元编程**:Lisp 语言的强大之处在于其自身可被用作编程工具,了解如何在 Python 中实现类似的功能。 完成此项目后,你不仅能...

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

    在Python中,我们也能看到很多LISP的影子,尤其是在使用函数、高阶函数、闭包以及列表推导等方面。 函数式编程是一种编程范式,它将计算视为数学函数的求值,并强调避免可变状态和副作用。在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。 一些代码基于的 。 特征 完整功能列表...

    Land of Lisp、Machine Learning in Action

    至于压缩包中的其他文件,如《21天学通Visual C++第2版》和《21天学通Visual Basic》扫描版,它们是关于C++和Basic编程的教材,虽然不是直接与Lisp和机器学习相关,但它们提供了基础编程知识,有助于理解任何编程...

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

    在Python中,我们需要实现一个函数来解析和执行Lisp的`if`表达式,如`(if (< 1 2) "less" "greater")`。这需要我们理解并实现逻辑判断和分支结构。 在Lisp中,**变量**的使用十分常见。我们需要创建一个符号表...

    Lisp语言入门.pdf

    3. **软件开发**:虽然Lisp在商业应用中不如Java或Python那样普遍,但在某些特定场景下,如构建高度定制化的软件系统,Lisp仍然表现出色。 4. **科学研究**:Lisp的灵活和强大使得它在科学研究领域中也有一席之地,...

    Python-用CommonLisp实现的NES模拟器

    总结,"Python-用CommonLisp实现的NES模拟器"是一个结合了两种语言优点的项目,展现了在游戏开发中如何利用不同语言的特长,以实现复杂的功能。Python的易用性和通用性结合Common Lisp的灵活性和效率,为开发者提供...

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

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

    LISP语言在CAD方面的运用.pdf

    LISP语言在CAD领域的运用十分广泛,不仅有助于提高绘图效率和质量,还能够帮助工程师自动化处理复杂任务,提升工作效率,同时在测绘领域也具有重要的应用价值,为工程师提供了强大的工具来处理图形数据和实现数据...

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

    Lisp语言作为最早期的函数式编程语言之一,其语言的简明性和表达能力让它在CAD二次开发中占有一席之地。它可以通过调用CAD内部命令简化编程过程,实现复杂计算的自动化。例如,Lisp程序可以自动判断桩号点位置的线段...

    lisp语言陈光喜2005

    根据给定的信息,“lisp语言陈光喜2005”这一资料主要介绍了Lisp语言的基础知识和技术要点。下面将详细解析标题、描述以及部分文本中的关键知识点。 ### Lisp语言概述 Lisp(LISt Processing language)是一种历史...

    AutoLISP 编程.zip_autoLisp编程_autolisp_autolisp教程_lisp编程

    在AutoLISP中,你可以编写函数和过程来控制AutoCAD,实现自动化绘图、数据管理和用户界面定制等功能。LISP(LISt Processing)语言以其独特的语法——使用括号表示结构和表达式——而闻名,它的设计哲学是使代码更...

    LISP语言(马希文).pdf

    LISP语言是一种古老的编程语言,由马希文和宋和柔编著的《LISP语言》一书是高等学校计算机专业的试用教材,该书首次出版于1980年7月,并在1990年进行了重新印刷。此书详细介绍了LISP的基本语法和表达式的概念,强调...

    Lisp语言.陈光喜.2005.pdf

    Lisp中的条件语句主要是通过 `if` 和 `cond` 实现的。`if` 提供了简单的条件判断结构,而 `cond` 则更适用于多条件的情况。例如: ```lisp (if (> x 10) (print "x is greater than 10") (print "x is less than ...

Global site tag (gtag.js) - Google Analytics