GParser 文档

Python Final Project


Parser Combinator Library

关于Python课程的Final Project,本人综合考虑了个人兴趣、项目难度等多个方面,并结合自身水平,最终决定用Python编写Parser Combinator库。希望通过编写这一Python Parser Combinator库,能够将自己心中对于易用性、简洁性、上手容易程度都颇为合理的Parser库的理解,用Python版API较好地呈现出来。

基于以上的目标,此Parser库会以用户最简单的上手程度、最少的心智负担为主要争取目标,同时提供更为强大的、更富表现力的表达式,来完成对任何字符组合的解析任务。因此,尽管此库运用了许多函数式编程的概念,但是用户并不需要补充任何相关知识,也可较好地运用此库完成任务。

目前该库取名Gparser,是属于Gaufoo的个人项目,并开源于Github,欢迎各位指正批评。接下来的文档,将详细讲解如何使用Gparser库。


Parser Combinator 概念

什么是Parser

简单地说:给定一个字符流,比如一个字符串、一段代码、一篇文章等,输入到Parser,会把原本平坦的文本改造成有层次有内涵的结构。

什么是Parser Combinator

由于函数式语言里函数是头等公民,可以把一个函数作为另一个函数的参数,一个函数可以产生出另外一个函数。基于这样的特点,我们可以把一个Parser作为参数传给另外一个Parser,一个Parser也可以产生另外一个新的Parser。有了这样的结合性,我们就可以实现Parser Combinator。
它是一种基于递归下降原理的非常简洁的Parser实现方法。

为什么是Parser Combinator

Parser Combinator在模块化、可读性、易维护等方面有着无可比拟的优势。
举个简单的例子:就像小孩子读文章,一开始他只认识一个一个字,拿着手指一个字一个字地点着读。接着他学会了把字结合起来变成词,认识词后又学会如何识别句子(很可能是因为学会了标点符号),慢慢长大后他便一目十行,脑中自然形成了文章的概念。Parser Combinator就很像这样一步一步组合的过程,非常符合人类直觉,所以编写起来相当容易。


Get Started

  • 下载

首先把项目下载下来,*nix下使用命令行:

1
$ git clone https://github.com/zhongzc/gparser.git

Windows用户可自行前往Github下载。

  • 安装

*nix下使用命令行:

1
$ sudo python3 setup.py install

  • 使用
1
2
3
4
5
6
$ python3
Python 3.6.7 (default, Oct 25 2018, 09:16:13)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gparser as gp
...
  • 删除

要删除egg文件:

1
$ sudo rm -rf /usr/local/lib/pythonX.X/site-packages/[PACKAGE].egg

然后修改site-packages/easy-install.pth,删除相应行。


Hello World

编写最简单的Parser:

1
2
3
4
5
6
import gparser as gp # (1)
dot = gp.char('.') # (2)
result, restTxt = dot.run('./') # (3)
print(result)
print(restTxt) # (4)

  1. 将Gparser库引入到环境内,并取别名为gp
  2. char函数能够解析一个字符,此处解析一个.字符,并返回一个Parser对象
  3. Parser对象中的run方法可以应用于待解析的字符串,返回State对象。State包含解析结果(Success | ParseError)对象和剩余字符串(LocatedText)对象,可以用Python中的多值匹配语法进行赋值。此处result即为解析结果对象,restTxt即为剩余字符串对象
  4. 打印解析结果和剩余字符串

输出如下:

1
2
3
4
5
6
7
# print(result)
Success(value=Result('.')) # (1)
# print(restTxt)
(1,2) # (2)
./
^ # (3)

  1. 解析成功,返回Success对象,里面包含Result对象,可供提取或者进一步的处理,如何进行进一步处理会在文档后面进行介绍
  2. 表明解析到的位置坐标,这里表示解析到第1行第2列的/
  3. 用用户友好的方式展示解析到的行以及待解析字符所在位置

解析结果result的类型可能是Success或者ParseError,可以通过get方法提取解析成功结果,假设解析失败,get方法会抛出异常:

1
2
3
4
5
...
print(result.get())
# 输出:
# .

要想运行Parser对象进行解析,还有另外一个函数run_strict,和run最大的区别是:run不进行字符串结尾(即EOF)进行检测,而run_strict如果解析到最后没有消耗完整个字符串,则视为解析错误。

1
2
3
4
...
result, restTxt = dot.run_strict('.foo')
print(result)
print(restTxt)

输出结果如下:

1
2
3
4
5
6
7
# print(result)
ParseError(msg='Excepted: <EOF>') # (1)
# print(restTxt)
(1,2)
.foo
^

  1. 解析错误返回ParseError对象,里面包含错误信息Excepted: <EOF>,向用户提示解析错误的原因

编写Parser

字符串参数

String

string函数与char函数类似,只不过从解析一个字符变成解析一个字符串,例子(接下来的例子都将省略import等语句,均用...代指):

1
2
3
4
5
6
7
8
9
10
11
...
pStr = gp.string('abc')
result, restTxt = pStr.run('abc')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (1,4)
# abc
# ^

假如给定的待解析字符串不能成功解析,结果如下:

1
2
3
4
5
6
7
8
9
10
...
result, restTxt = pStr.run('defgh')
print(result)
# ParseError(msg='Excepted: a')
print(restTxt)
# (1,1)
# defgh
# ^


One Of

one_of函数能够接收一个字符串参数,并能成功解析字符串中的其中一个字符:

1
2
3
4
5
6
7
8
9
10
11
...
pOneOf = gp.one_of('1234')
result, restTxt = pOneOf.run('1k')
print(result)
# Success(value=Result('1'))
print(restTxt)
# (1,2)
# 1k
# ^


None Of

none_of函数解析字符串参数中不包含的字符:

1
2
3
4
5
6
7
8
9
10
11
...
pNoneOf = gp.none_of('1234')
result, restTxt = pOneOf.run('1k')
print(result)
# ParseError(msg='Excepted: none of 1, 2, 3, 4')
print(restTxt)
# (1,1)
# 1k
# ^


Regex

接受一个r正则表达式,若从待解析字符串头开始匹配成功,则返回成功的结果:

1
2
3
4
5
6
7
8
9
10
11
...
pReg = gp.regex(r'[A-Za-z_][A-Za-z0-9_]*')
result, restTxt = pReg.run('_hey1')
print(result)
# Success(value=Result('_hey1'))
print(restTxt)
# (1,6)
# _hey1
# ^

组合子

Many

many函数是一个解析组合子(Parser Combinator)。它能够接受一个Parser对象参数,并能进行0次或多次的应用此Parser

1
2
3
4
5
6
7
8
9
10
11
...
pManyDot = gp.many(dot)
result, restTxt = pManyDot.run('.......!')
print(result)
# Success(value=Result(['.', '.', '.', '.', '.', '.', '.']))
print(restTxt)
# (1,8)
# .......!
# ^

函数库里还有另一个类似的函数:many1,和many的主要区别是:many1里的Parser能够应用1次或以上many则应用0次或以上


Skip

skip函数同样是一个组合子,它接受一个Parser对象参数,如果成功解析,则将结果直接抛弃:

1
2
3
4
5
6
7
8
9
10
11
...
pSkip = gp.skip(dot)
result, restTxt = pSkip.run('.......!')
print(result)
# Success(value=Result())
print(restTxt)
# (1,2)
# .......!
# ^

函数库中还有skip_manyskip_many1,他们的效果和skip(many(SOME_PARSER))skip(many1(SOME_PARSER))相同:

1
2
3
4
5
6
7
8
9
10
11
...
pSkipMany = gp.skip_many(dot)
result, restTxt = pSkipMany.run('.......!')
print(result)
# Success(value=Result())
print(restTxt)
# (1,8)
# .......!
# ^

skipmany的组合:

1
2
3
4
5
6
7
8
9
10
11
...
pSkipManyy = gp.skip(gp.many(dot))
result, restTxt = pSkipManyy.run('.......!')
print(result)
# Success(value=Result())
print(restTxt)
# (1,8)
# .......!
# ^


Next

Next组合子组合两个Parser,当第一个Parser成功解析后,抛弃结果,然后继续解析箭头指向的第二个Parser。左Next语法为L_PARSER >> R_PARSER,右Next语法为L_PARSER << R_PARSER

当Next的方向为>>

1
2
3
4
5
6
7
8
9
10
11
12
13
...
pABC = gp.string('abc')
pDEF = gp.string('def')
comb = pABC >> pDEF
result, restTxt = comb.run('abcdef')
print(result)
# Success(value=Result('def'))
print(restTxt)
# (1,7)
# abcdef
# ^

当Next的方向为<<

1
2
3
4
5
6
7
8
9
10
11
...
comb = pABC << pDEF
result, restTxt = comb.run('abcdef')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (1,7)
# abcdef
# ^

实际上不止可以组合两个Parser,对于任意数量的Parser,都可以通过Next来进行组合,取最终流向的Parser的应用结果为解析结果:

1
2
3
4
5
6
7
8
9
10
11
...
comb = pDEF >> pABC << pDEF
result, restTxt = comb.run('defabcdef')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (1,10)
# defabcdef
# ^

有时候大量组合过于繁杂,建议用括号保持可读性。另一例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
comb = pABC << pDEF >> pDEF >> pABC
# 实际上的组合方向为:
comb = ((pABC << pDEF) >> pDEF) >> pABC
# 最终解析结果为最后一个pABC的应用结果
result, restTxt = comb.run('abcdefdefabc')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (1,13)
# abcdefdefabc
#  ^


Just

在介绍了Next组合子后,just函数的出现才有了更加明显的意义。just函数产生的Parser实际上不进行任何的字符消耗,但是能将自身接受的参数当做成功解析的结果返回,和Next组合起来能够共同发挥巨大的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
aObj = (1, 2, 3)
pOTT = gp.string('123') >> gp.just(aObj)
result, restTxt = pOTT.run('123')
print(result)
# Success(value=Result((1, 2, 3)))
print(restTxt)
# (1,4)
# 123
# ^


Or

Or组合子能够组合两个Parser对象,当第一个Parser解析失败后不会直接报告失败,而是会尝试接着第二个Parser。Or组合子语法为L_PARSER | R_PARSER。Or原则上不进行回溯,即第一个Parser失败后,第二个Parser会从第一个Parser的失败处继续解析。
可以结合Maybe组合子将Or组合子改造成回溯版本,Maybe组合子接受一个Parser对象作为参数,当解析失败时会回退待解析字符串。

  • 不回溯
1
2
3
4
5
6
7
8
9
10
11
12
13
...
pABC = gp.string('abc')
pDEF = gp.string('def')
pOr = pABC | pDEF
result, restTxt = pOr.run('abdefg')
print(result)
# Success(value=Result('def'))
print(restTxt)
# (1,6)
# abdefg
# ^
  • 回溯
1
2
3
4
5
6
7
8
9
10
11
...
pOrBack = gp.maybe(pABC) | pDEF
result, restTxt = pOrBack.run('abdefg')
print(result)
# ParseError(msg='Excepted: d')
print(restTxt)
# (1,1)
# abdefg
# ^

Between

between组合接收三个Parser对象参数,忽略第一个和最后一个参数的解析结果,取中间的应用结果为解析结果,即between(p1, p2, p3)的效果实际上与p1 >> p2 << p3效果一致。取函数别名仅为了更明确组合后的Parser的作用,常用于解析被括弧包围的中间值,实际可以用Next组合子替代。


Separate By

sep_by组合子接受两个Parser对象作为参数,其中第二个Parser作为第一个Parser的分隔符,组合后的Parser将多次解析被分割开的字符串,最终返回解析成功后的列表:

1
2
3
4
5
6
7
8
9
10
11
12
...
pABC = gp.string('abc')
pSep = gp.sep_by(pABC, gp.char(','))
result, restTxt = pSep.run('abc,abc,abc')
print(result)
# Success(value=Result(['abc', 'abc', 'abc']))
print(restTxt)
# (1,12)
# abc,abc,abc
# ^

与其余组合子的命名习惯相同,sep_by有可能返回空列表,sep_by1则会解析至少一个结果。


Chain

Chain组合子分为chain_leftchain_right,均接受两个Parser作为参数,第一个参数为node,第二个参数为op

类似sep_byop作为分隔符将分割多个node。与sep_by不同的是,sep_bysep作为分隔符,其解析结果将被抛弃,而op解析成功后需要返回一个函数。该函数能够将被op分割的node的解析结果组合起来。最常见的op的构造办法,是利用Next和justpOpStr >> just(func)chain_left是从左向右组合,chain_right则为从右向左组合。

下面用tuple演示chain_leftchain_right的不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
pABC = gp.string('abc')
pCatL = gp.chain_left(pABC, char('.') >> gp.just(lambda x, y: (x, y)))
result, restTxt = pCatL.run('abc.abc.abc')
print(result)
# Success(value=Result((('abc', 'abc'), 'abc')))
print(restTxt)
# (1,12)
# abc.abc.abc
# ^
pCatR = gp.chain_right(pABC, char('.') >> gp.just(lambda x, y: (x, y)))
result, restTxt = pCatR.run('abc.abc.abc')
print(result)
# Success(value=Result(('abc', ('abc', 'abc'))))
print(restTxt)
# (1,12)
# abc.abc.abc
# ^

实际上,现在看不懂Chain组合子的用法无大碍,在特定情况下它的精简和方便一定会让你印象深刻。


Token

Token是一个非常方便的组合子,能够忽略待解析字符串的前后空白符,既可以用token函数组合出来,也可以用Parser自带的tk方法达到同等效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
pABC = gp.string('abc')
pTok = gp.token(pABC)
result, restTxt = pTok.run(' \n abc \t ')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (2,9)
# abc
# ^
pTk = pABC.tk()
result, restTxt = pTk.run(' \n abc \t ')
print(result)
# Success(value=Result('abc'))
print(restTxt)
# (2,9)
# abc
# ^

内置解析函数

函数库中提供少量简便的解析函数,列举如下:

函数名 功能
space 解析空白符
spaces 解析多个空白符
digit 解析数字字符
alpha 解析大小写字母字符
number 解析正负整数
eof 解析字符串结束

错误信息

label函数或者Parser对象中的label方法能够修改解析错误后显示的错误原因,能以更清晰明了的方式让Parser的使用者明白解析错误原因:

1
2
3
4
5
6
7
8
9
10
11
12
...
pABC = gp.string('abc')
pL = pABC.label('I need "abc"!')
result, restTxt = pL.run('hello')
print(result)
# ParseError(msg='I need "abc"!')
print(restTxt)
# (1,1)
# hello
# ^

Map组合(核心)

接下来将介绍Gparser库最为核心的API:Map组合表达式。

连接组合子

在GParser库中,有一个很方便的组合子:+,可以连接多个Parser,从而获得一组解析结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
pABC = gp.string('abc')
pDEF = gp.string('def')
pDot = gp.string('.')
pCat = pABC + pDEF + pDot
result, restTxt = pCat.run('abcdef.')
print(result)
# Success(value=('abc', 'def', '.'))
print(restTxt)
# (1,8)
# abcdef.
# ^

有时候希望忽略某个Parser的解析结果,可以在该Parser对象前加上~符,这里忽略pDEF的解析结果:

1
2
3
4
5
6
7
8
9
10
...
pCat2 = pABC + ~pDEF + pDot
result, restTxt = pCat2.run('abcdef.')
print(result)
# Success(value=('abc', '.'))
print(restTxt)
# (1,8)
# abcdef.
# ^

Map

GParser带来的不仅仅是+所具备的强大组合性,更重要的是组合后的Parsermap方法,为多个解析结果提供了组合的可能。合理使用+map,能够非常轻易地编写出简洁易懂却又强大健壮的Parser。

对于未使用+连接的Parsermap为其提供了转化的能力。Parsermap方法接受一个函数作为参数,在未使用+连接的情况下,该函数参数接受一个参数,转化该参数并返回。当Parser成功解析完原始的结果,map方法将让函数参数应用此原始结果,转化成最终结果。值得注意的是,这里的原始结果是相对的,一个应用过mapParser的解析结果,可以作为下一个map的原始结果,即,Parser可以组合多个map。简单例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
pABC = gp.string('abc')
pM1 = pABC.map(lambda x: len(x))
result, restTxt = pM1.run('abc')
print(result)
# Success(value=Results(3))
print(restTxt)
# (1,4)
# abc
# ^
pM2 = pM1.map(lambda x: x * 2)
result, restTxt = pM2.run('abc')
print(result)
# Success(value=Results(6))
print(restTxt)
# (1,4)
# abc
# ^

map结合上+map接受的函数参数的参数数量将发生变化。假设+连接了3个无前置~Parser,则组合后的整个Parsermap方法接受的函数参数应该是一个三参函数,且顺序与+连接的Parser顺序一一对应。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
pABC = gp.string('abc')
pDEF = gp.string('def')
pDot = gp.string('.')
pMulMap = (pABC + pDEF + pDot).map(lambda x, y, z: x[2] + z + y[1])
result, restTxt = pMulMap.run('abcdef.')
print(result)
# Success(value=Results('c.e'))
print(restTxt)
# (1,8)
# abcdef.
# ^

大部分情况下,只是需要把所有用+连接的string结果拼接起来,Gparser库提供了简便的concat函数,使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
pABC = gp.string('abc')
pDEF = gp.string('def')
pDot = gp.string('.')
pMulMapCon = (pABC + pDEF + pDot).map(gp.concat)
result, restTxt = pMulMapCon.run('abcdef.')
print(result)
# Success(value=Results('abcdef.'))
print(restTxt)
# (1,8)
# abcdef.
# ^

对于带有~+组合表达式,~所连接的Parser将不占用map函数参数的参数位:

1
2
3
4
5
6
7
8
9
10
...
pMulSkipMap = (pABC + ~pDEF + pDot).map(lambda abc, dot: dot * len(abc))
result, restTxt = pMulSkipMap.run('abcdef.')
print(result)
# Success(value=Results('...'))
print(restTxt)
# (1,8)
# abcdef.
# ^

注意

Parser内含有or_not方法,在Map表达式中使用要十分留意,非常容易出错。若+连接的表达式中含有or_not,其后的map的函数参数只能使用可变长参。


Parser实战

四则运算表达式

有关详细的四则运算表达式形式化表达可以看我以前写的这篇文章,这里只进行简单的回顾。

四则运算表达式有三个抽象组合而成,分别是ExpFactorTerm

  • Exp = Factor (( ‘+’ | ‘-‘ ) Factor)*
  • Factor = Term (( ‘*‘ | ‘/‘ ) Term)*
  • Term = <数字> | ‘(‘ Exp ‘)’

从以上表达式中可以看出,四则运算表达式是递归表达的,那么GParser能不能胜任这种递归表达呢?答案是肯定的。

Number

1
2
3
import gparser as gp
pNum = gp.many1(gp.digit()).map(lambda ds: int(''.join(ds)))

Operator

1
2
3
4
5
...
pAdd = gp.char('+') >> gp.just(lambda x, y: x + y)
pSub = gp.char('-') >> gp.just(lambda x, y: x - y)
pMul = gp.char('*') >> gp.just(lambda x, y: x * y)
pDiv = gp.char('/') >> gp.just(lambda x, y: x / y)

Abstraction

由于Python不能够直接引用未在之前出现过的参数,这样使得编写递归定义的Parser比较棘手。GParser提供了undef函数作为解决方案,它能够提前声明Parser以占位变量,可在后续中利用assign方法进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
...
pExp = gp.undef()
pFactor = gp.undef()
pTerm = gp.undef()
# Exp = Factor (( '+' | '-' ) Factor)*
pExp.assign(gp.chain_left(pFactor, pAdd | pSub))
# Factor = Term (( '*' | '/' ) Term)*
pFactor.assign(gp.chain_left(pTerm, pMul | pDiv))
# Term = <数字> | '(' Exp ')'
pTerm.assign(pNum | gp.between(gp.char('('), pExp, gp.char(')')))

这样就完成了pExp这一四则运算表达式的Parser,下面尝试运行:

1
2
3
4
5
6
7
8
9
10
11
12
...
result, restTxt = pExp.run('(85+31)*5')
print(result)
# Success(value=Results(580))
print(restTxt)
# (1,10)
# (85+31)*5
# ^
print((85+31)*5)
# 580

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import gparser as gp
pNum = gp.many1(gp.digit()).map(lambda ds: int(''.join(ds)))
pAdd = gp.char('+') >> gp.just(lambda x, y: x + y)
pSub = gp.char('-') >> gp.just(lambda x, y: x - y)
pMul = gp.char('*') >> gp.just(lambda x, y: x * y)
pDiv = gp.char('/') >> gp.just(lambda x, y: x / y)
pExp = gp.undef()
pFactor = gp.undef()
pTerm = gp.undef()
# Exp = Factor (( '+' | '-' ) Factor)*
pExp.assign(gp.chain_left(pFactor, pAdd | pSub))
# Factor = Term (( '*' | '/' ) Term)*
pFactor.assign(gp.chain_left(pTerm, pMul | pDiv))
# Term = <数字> | '(' Exp ')'
pTerm.assign(pNum | gp.between(gp.char('('), pExp, gp.char(')')))

JSON

ADT

1
2
3
4
5
6
7
8
9
10
from collections import namedtuple
JNull = namedtuple('JNull', [])
JTrue = namedtuple('JTrue', [])
JFalse = namedtuple('JFalse', [])
JNum = namedtuple('JNum', ['value'])
JStr = namedtuple('JStr', ['value'])
JObj = namedtuple('JObj', ['value'])
JPair = namedtuple('JPair', ['key', 'value'])
JArr = namedtuple('JArr', ['value'])

常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
import gparser as gp
# 数字
digits = gp.regex(r'[0-9]+')
exponent = (gp.one_of('eE') + gp.one_of('-+').or_not() + digits).map(gp.concat)
fractional = (gp.char('.') + digits).map(gp.concat)
integral = gp.char('0') | gp.regex(r'[1-9][0-9]*')
number = (gp.one_of('-+').or_not() + integral + fractional.or_not() + exponent.or_not()) \
.map(gp.concat).map(lambda n: JNum(float(n))).tk()
# null, true, false
null = gp.string('null') >> gp.just(JNull()).tk()
false = gp.string('false') >> gp.just(JFalse()).tk()
true = gp.string('true') >> gp.just(JTrue()).tk()
# string
string = gp.between(gp.char('"'), gp.many(gp.none_of('"')), gp.char('"')) \
.map(lambda cs: ''.join(cs)).map(JStr).tk()

json对象

1
2
3
4
5
6
7
8
9
10
...
jExp = gp.undef()
array = gp.between(gp.char('['), jExp.sep_by(gp.char(',').tk()), gp.char(']')) \
.map(JArr).tk()
pair = (string + ~gp.char(':') + jExp).map(lambda k, v: JPair(k, v)).tk()
obj = gp.between(gp.char('{'), pair.sep_by(gp.char(',').tk()), gp.char('}')) \
.map(JObj).tk()
jExp.assign((obj | array | string | true | false | null | number).tk())

运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
str = """{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": 10021
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}"""
res, t = jExp.run(str)
print(res)

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import gparser as gp
from collections import namedtuple
JNull = namedtuple('JNull', [])
JTrue = namedtuple('JTrue', [])
JFalse = namedtuple('JFalse', [])
JNum = namedtuple('JNum', ['value'])
JStr = namedtuple('JStr', ['value'])
JObj = namedtuple('JObj', ['value'])
JPair = namedtuple('JPair', ['key', 'value'])
JArr = namedtuple('JArr', ['value'])
digits = gp.regex(r'[0-9]+')
exponent = (gp.one_of('eE') + gp.one_of('-+').or_not() + digits).map(gp.concat)
fractional = (gp.char('.') + digits).map(gp.concat)
integral = gp.char('0') | gp.regex(r'[1-9][0-9]*')
number = (gp.one_of('-+').or_not() + integral + fractional.or_not() + exponent.or_not()) \
.map(gp.concat).map(lambda n: JNum(float(n))).tk()
null = gp.string('null') >> gp.just(JNull()).tk()
false = gp.string('false') >> gp.just(JFalse()).tk()
true = gp.string('true') >> gp.just(JTrue()).tk()
string = gp.between(gp.char('"'), gp.many(gp.none_of('"')), gp.char('"')) \
.map(lambda cs: ''.join(cs)).map(JStr).tk()
jExp = gp.undef()
array = gp.between(gp.char('['), jExp.sep_by(gp.char(',').tk()), gp.char(']')) \
.map(JArr).tk()
pair = (string + ~gp.char(':') + jExp).map(lambda k, v: JPair(k, v)).tk()
obj = gp.between(gp.char('{'), pair.sep_by(gp.char(',').tk()), gp.char('}')) \
.map(JObj).tk()
jExp.assign((obj | array | string | true | false | null | number).tk())