PyParsec 升级至 0.7.3

PyParsec 是 Python 3 的 parsec 移植。这个项目原本是一个非常微型的练手作品,是有一次 archmmit 会场里,我和老友赖勇浩聊天的时候,随手写出来的。因为 Python 是动态类型语言,很多 Haskell 里基于静态类型构建的类型约束也就失去了作用——当然,另一方面说,动态语言项目,很多静态语言的技术要点也不用考虑。
后面有很长一段时间,我都没太去想这个项目,对于我来说,验证了 python 可以作类似的东西,也就达成了目的。但是近期我加入到 csdn 的 ai 团队,有一些开发工作需要编写一些微型的解释器,或者复杂文本的分析程序。于是,我就将它又翻了出来。
除了缺少一些我在工作中用到的算子,pyparsec 还有一些比较突出的不足,比如算子都是很朴素的函数或者可调用对象,这导致复杂的算子写起来可读性会受影响,例如这个不太复杂的例子:

p = many(ch('i'))

显然写成

p = ch('i').many


更友善一些。
没有第一时间这样做,是因为 many 是一个组合子,它是基于 parsec 规范定义的算子,不应该成为一个地位超然的东西。基本法定下来,大家都要遵守,要么将来有办法给Parsec算子任意挂载新的扩展方法,要么大家都不能直接出现在 Parsec 的方法列表里,都不要有钦点的意思。
最终,在 scala 版本中,这些组合子以 typeclass 的风格成为了算子的扩展方法,它们可以通过 import 对应的隐式类型(在 Scala 3中是 extension)而任意扩展,外部扩展和内置算子地位一致,并不会破坏 parsec 的设计。
而在 python 中,我做了一些折衷。首先,为了解环形引用,也为了将来用于自己实现新的扩展方法方便,我定义了一个 combinator 类型:

class Combinator:
    def __init__(self, parsec):
        self.parsec = parsec

    def __call__(self, st):
        return self.parsec(st)

    def bind(self, continuation):
        return self.parsec.bind(continuation)

    def then(self, p):
        return self.parsec.then(p)

    def over(self, p):
        return self.parsec.over(p)


需要注意的是这个类型虽然接口与 Parsec 类型一致,但是实现并不相同。
然后再实现一个内置组合子的修饰器类型:

class BuiltIn(Combinator):
    def __init__(self, parsec):
        super().__init__(parsec)
        self.parsec = parsec

    @property
    def head(self):
        from .combinator import ahead
        return ahead(self.parsec)

    @property
    def attempt(self):
        from .combinator import attempt
        return attempt(self.parsec)

    @property
    def many(self):
        from .combinator import many
        return many(self.parsec)

    @property
    def many1(self):
        from .combinator import many1
        return many1(self.parsec)

    @property
    def skip(self):
        from .combinator import skip
        return skip(self.parsec)

    @property
    def skip1(self):
        from .combinator import skip1
        return skip1(self.parsec)

    def sepBy(self, by):
        from .combinator import sep
        return sep(self.parsec, by)

    def sepBy1(self, by):
        from .combinator import sep1
        return sep1(self.parsec, by)

    def otherwise(self, other):
        from .combinator import choice
        return choice(self.parsec.attempt, other)

    def manyTill(self, end):
        from .combinator import manyTill
        return manyTill(self.parsec, end)

这个类型可以直接挂在其它类型或函数的定义上,成为一个修饰器(decorator),而这里还是给了它一点特权,Parsec 类型其实是继承自 Builtin:

#!/usr/bin/env python3
# coding:utf-8

from .builtin import BuiltIn


class Parsec(BuiltIn):
    def __init__(self, parsec):
        super().__init__(parsec)
        self.parsec = BuiltIn(parsec)

    def __call__(self, st):
        return self.parsec(st)

    def bind(self, continuation):
        def bind(st):
            binder = continuation(self.parsec(st))
            return binder(st)

        return Parsec(bind)

    def then(self, p):
        def then(st):
            self.parsec(st)
            return p(st)

        return Parsec(then)

    def over(self, p):
        def over(st):
            re = self.parsec(st)
            p(st)
            return re

        return Parsec(over)

虽然我花了一点儿时间想办法解决了环形引用,但是这样只需要一个修饰器,就自动拥有了 builtin 方法,如同 python 的 builtins ,也不需要额外 import 。
而外部定义的修饰器扩展,需要和 Parsec 修饰器一起使用,组合修饰在算子定义上。
当然,更理想的办法是让任意的扩展方法可以自动挂载,但是 Python 既没有 typclass,也没有很方便的reopen能力,meta class也非常容易被破坏。修饰器是一个不够简洁,但是相对简单的方案。最终 pyparsec 选择了这个路线。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ccat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值