Spark 是一种用 Python 编写的强大的、通用的解析器/编译器框架。在某些方面,Spark 所提供的比 SimpleParse 或其它 Python 解析器提供的都要多,下面进行学习研究。
然而,因为它完全是用 Python 编写的,所以速度也会比较慢。David 在本文中讨论了 Spark 模块,给出了一些代码样本,解释了它的用途,并对其应用领域提供了一些建议。本文中会介绍一些解析的基本概念,并对 Spark 模块进行了讨论。解析框架是一个内容丰富的主题,它值得我们多花时间去全面了解;
这两篇文章为读者和我自己都开了一个好头。在日常的编程中,我经常需要标识存在于文本文档中的部件和结构,这些文档包括:日志文件、配置文件、定界的数据以及格式更自由的(但还是半结构化的)报表格式。所有这些文档都拥有它们自己的“小语言”,用于规定什么能够出现在文档内。我编写这些非正式解析任务的程序的方法总是有点象大杂烩。
其中包括定制状态机、正则表达式以及上下文驱动的字符串测试。这些程序中的模式大概总是这样:“读一些文本,弄清是否可以用它来做些什么,然后可能再多读一些文本,一直尝试下去。”
解析器将文档中部件和结构的描述提炼成简明、清晰和说明性的规则,确定由什么组成文档。大多数正式的解析器都使用扩展巴科斯范式(Extended Backus-Naur Form,EBNF)上的变体来描述它们所描述的语言的“语法”。
基本上,EBNF 语法对您可能在文档中找到的部件赋予名称;另外,较大的部件通常由较小的部件组成。小部件在较大的部件中出现的频率和顺序由操作符指定。举例来说,清单 1 是 EBNF 语法 typographify.def,我们在 SimpleParse 那篇文章中见到过这个语法(其它工具运行的方式稍有不同):
- #-*- encoding: gb2312 -*-
- import quopri
- a = "only a test数据"
- b = quopri.encodestring(a) # 对字符串编码
- print b
- print quopri.decodestring(b) # 对字符串解码
- import StringIO
- c = StringIO.StringIO()
- d = StringIO.StringIO()
- e = StringIO.StringIO()
- c.write(a)
- c.seek(0)
- quopri.encode(c, d, 0) # 编码StringIO中的数据, 第三个参数0表示不对空格和tab符号编码,为1表示进行编码
- print d.getvalue()
- d.seek(0)
- quopri.decode(d, e) # 解码StringIO中的数据
- print e.getvalue()
- f1 = open("aaa.txt", "w")
- f1.write(a)
- f1.close()
- f1 = open("aaa.txt", "r")
- f2 = open("bbb.txt", "w")
- quopri.encode(f1, f2, 0) # 编码aaa.txt中的数据到bbb.txt
- f1.close()
- f2.close()
- print open("bbb.txt", "r").read()
这里有一个有趣的地方。WordScanner 本身是一个完美的扫描程序类;但 Spark 扫描程序类本身可以通过继承进一步特化:子正则表达式模式在父正则表达式之前匹配,而如果需要,子方法/正则表达式可以覆盖父方法/正则表达式。
所以,WordPlusScanner 将在 WordScanner 之前对特化进行匹配(可能会因此先获取一些字节)。模式文档字符串中允许使用任何正则表达式(举例来说,.t_contraction() 方法包含模式中的一个“向后插入”)。查找记号的确有一点意思,但真正有意思的是如何向记号列表应用语法。解析阶段在记号列表的基础上创建任意的树结构。它只是指定了表达式语法而已。
Spark 有好几种创建 AST 的方法。“手工”的方法是特化 GenericParser 类。在这种情况下,具体子解析器会提供很多方法。方法名的形式为 p_foobar(self, args)。每个这样的方法的文档字符串都包含一个或多个模式到名称的分配。只要语法表达式匹配,每种方法就可以包含任何要执行的代码。
【编辑推荐】