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 选择了这个路线。



