我正在尝试使用PLY lex / yacc解析PDF,并且我遇到了关于管理NUMBER
令牌,数组和indirect_references的yacc解析规则的问题。
相关来源:
def p_value_list(t):
r'''value_list : value_list value
| value'''
if t.slice[0] == 'item_list':
t[0] = {'type':'value_list', 'children' : t[0]['children'] + [t[1]] }
else:
t[0] = {'type':'value_list', 'children' : [t[1]] }
pass
def p_value(t):
r'''value : dictionary
| array
| indirect_reference
| NUMBER
| HEX
| STREAM
| TEXT
| BOOL
| empty'''
t[0] = t[1]
pass
def p_indirect_reference(t):
r'''indirect_reference : NUMBER NUMBER KEY_R'''
t[0] = {'type':'indirect_reference', 'children' : [t[1], t[2]] }
pass
def p_array(t):
r'''array : LBRACKET value_list RBRACKET'''
t[0] = {'type':'array', 'children' : t[2] }
pass
导致关于NUMBERS
的规则含糊不清(您是否有列表NUMBER NUMBER
或者您是否有间接引用NUMBER NUMBER KEY_R
) - 我得到的错误来自意外{{1}解析简单数组NUMBER
[0 0 0]
这样的错误
ERROR: Error : obj_list NUMBER NUMBER OBJ LTLT key_value_list ID LBRACKET NUMBER NUMBER . LexToken(NUMBER,'0',1,166273)
我认为假设它是一个包含两个ERROR: Error : obj_list NUMBER NUMBER OBJ LTLT key_value_list ID LBRACKET NUMBER NUMBER . LexToken(RBRACKET,']',1,88)
令牌的数组KEY_R
令牌NUMBER
勇敢的PDF规范和完整的源链接。
[1] https://github.com/opticaliqlusion/pypdf/blob/master/pypdf.py
[2] http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/pdf_reference_1-7.pdf
答案 0 :(得分:1)
这不是一个含糊不清的;语法是完全明确的。但是,它不是LR(1),并且LR(1)解析器无法确定是否移位或减少整数(如果后跟另一个整数)。 (语法是LR(2),因为第二个下一个标记足以决定;如果它是R
,则整数是间接引用中的第一个标记,应该移位。
解决问题要困难得多。理论上,您可以将任何LR(2)语法转换为LR(1)语法,但转换很麻烦,我不知道任何自动化工具。基本思想是扩展产品,以避免在遇到足够的上下文之前需要减少,然后根据需要修复解析树。
(对于此问题的其他一些可能的解决方案,请参见下文。最后一个选项是我个人最喜欢的。)
这里是LR(2)→LR(1)技术的例证;你可以看到NUMBER
令牌在知道处置之前是如何累积的:
value_not_number
: dictionary
| array
| HEX
| STREAM
| TEXT
| BOOL
| empty
value_list
: value_list_ends_non_number
| value_list_ends_one_number
| value_list_ends_two_numbers
| value_list_ends_indref
|
value_list_ends_one_number
: value_list_ends_non_number NUMBER
value_list_ends_two_numbers
: value_list_ends_non_number NUMBER NUMBER
| value_list_ends_two_numbers NUMBER
value_list_ends_indref
: value_list_ends_two_numbers 'R'
value_list_ends_non_number
: value_list value_not_number
array : '[' value_list ']'
请注意,语法生成的解析树不太准确,因为它会将0 0 R
解析为value_list_ends_two_numbers
后跟R
。为了检索真正的解析树,value_list_ends_indref
的缩减操作需要从第一个子节点中窃取最后两个数字,使用它们来制作间接引用对象,然后将其添加到第一个子节点的末尾。所以带有动作的PLY语法可能如下所示:
def p_unit_productions(t):
r'''value_not_number
: dictionary
| array
| HEX
| STREAM
| TEXT
| BOOL
value_list
: value_list_ends_non_number
| value_list_ends_one_number
| value_list_ends_two_numbers
| value_list_ends_indref'''
t[0] = t[1]
def p_value_list_empty(t):
r'''value_list:'''
t[0] = []
def p_value_list_push(t):
r'''value_list_ends_one_number
: value_list_ends_non_number NUMBER
value_list_ends_two_numbers
: value_list_ends_two_numbers NUMBER
value_list_ends_non_number
: value_list value_not_number'''
t[0] = t[1]
t[0].append(t[2])
def p_value_list_push2(t):
r'''value_list_ends_two_numbers
: value_list_ends_non_number NUMBER NUMBER'''
t[0] = t[1]
t[0].append(t[2])
t[0].append(t[3])
def p_indirect_reference(t):
r'''value_list_ends_indref
: value_list_ends_two_numbers 'R' '''
t[0] = t[1]
gen = t[0].pop()
obj = t[0].pop()
t[0].append({'type': 'indirect_reference',
'children': [obj, gen] })
def p_array(t):
r'''array : '[' value_list ']' '''
t[0] = {'type':'array', 'children' : t[2] }
这与你的不完全相同:
value_list
对象类型(在我看来这是不必要的)。value: empty
是空的非终端,我删除了empty
;因为你不知道在两个非终端之间发现了多少空字符串,所以会导致真正的歧义。(我还没有测试过那个PLY碎片;如果有问题,请告诉我。我很容易就会发生错误。)
正如评论中所建议的那样,另一种方法是使用词汇扫描程序来识别整个序列(在这种情况下是间接引用),这需要扩展前瞻。
实际上,它使用词法扫描程序来实现后备解决方案。当语法中只有少数LR(2)状态时,这是一种常见的解决方案。例如,bison
本身使用此技术来区分生产的左侧和右侧的符号的使用。 (当你看到 - 或者看不到 - 符号后的冒号时,你可以分辨出差异,但你必须知道符号本身何时是第一个前瞻。)
在某些情况下,这似乎是一个更简单的解决方案,但在其他情况下,它会导致将解析器的复杂性简单地导出到扫描程序。例如,在这种特殊情况下,您可能使用最长匹配词汇消歧算法来区分R
和以R
开头的标识符标记,并且您可能有一个忽略空格的规则。这些都不会帮助您遵守规则来识别间接引用;您需要显式匹配空格,并且需要显式验证R
后面的内容不能形成更长的标识符。这不是非常复杂,但也不是微不足道的;您需要做一些工作来创建适当的测试覆盖率。
随着可能的扩展前瞻状态的数量增加,您会发现此解决方案越来越复杂。 (并不是说上面概述的解析器解决方案也很棒。)
由于语法本身并不含糊,因此如果您有一个可以生成GLR解析器的解析器生成器,则可以使用GLR解析器而不是LR(1)解析器。 GLR算法可以解析任何无上下文语法,但代价是:它在常见情况下略慢,在边缘情况下慢得多(在某些语法中,它可能需要二次或甚至三次),典型实现会延迟语义操作直到歧义得到解决,这意味着他们不一定按照预期的顺序执行。
在这种情况下,对此解决方案有更强烈的抑制作用:PLY
不会产生GLR语法。但是,bison
确实有一个GLR选项,将bison解析器包装到python模块中相对简单(如果重要的话,这很可能比PLY解析器更快)。
PDF源自Postscript,它是一种堆栈语言。堆栈语言通常不需要解析器;他们只有一个词法扫描器和一个堆栈。每个令牌都有一个相关的动作;对于许多令牌(例如文字),操作只是将令牌推送到堆栈上。其他令牌可能会有更复杂的行为。
例如,]
令牌通过弹出堆栈创建一个数组,直到找到[
并将弹出的元素放入新创建的数组对象中,然后将其推入堆栈
同样,R
令牌的动作是从堆栈顶部弹出两个东西;检查它们是否是数字;然后使用这两个数字构造一个间接引用对象,然后将其推入堆栈。
PLY在构建PDF堆栈解析器方面不会给你太多帮助,但它至少会为你构建词法分析器,这实际上是解析器中最复杂的部分。所需的堆栈操作并不多,而且没有一个特别具有挑战性。