PLY / YACC解析PDF上的冲突

时间:2016-07-28 05:25:17

标签: python pdf yacc ply

我正在尝试使用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

1 个答案:

答案 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对象类型(在我看来这是不必要的)。
  • 语法组织可能看起来有点奇怪,因为我定义的函数是按行动而不是非终端组织的,但PLY处理这个很好(http://www.dabeaz.com/ply/ply.html#ply_nn25)并导致更简单的操作
  • 根据PLY手册,假设您的value: empty是空的非终端,我删除了empty;因为你不知道在两个非终端之间发现了多少空字符串,所以会导致真正的歧义。
  • 我用单引号符号(http://www.dabeaz.com/ply/ply.html#ply_nn26)替换了单字符文字标记,因为我个人认为它使语法更具可读性。

(我还没有测试过那个PLY碎片;如果有问题,请告诉我。我很容易就会发生错误。)

其他可能的解决方案

1。使用词法扫描仪

正如评论中所建议的那样,另一种方法是使用词汇扫描程序来识别整个序列(在这种情况下是间接引用),这需要扩展前瞻。

实际上,它使用词法扫描程序来实现后备解决方案。当语法中只有少数LR(2)状态时,这是一种常见的解决方案。例如,bison本身使用此技术来区分生产的左侧和右侧的符号的使用。 (当你看到 - 或者看不到 - 符号后的冒号时,你可以分辨出差异,但你必须知道符号本身何时是第一个前瞻。)

在某些情况下,这似乎是一个更简单的解决方案,但在其他情况下,它会导致将解析器的复杂性简单地导出到扫描程序。例如,在这种特殊情况下,您可能使用最长匹配词汇消歧算法来区分R和以R开头的标识符标记,并且您可能有一个忽略空格的规则。这些都不会帮助您遵守规则来识别间接引用;您需要显式匹配空格,并且需要显式验证R后面的内容不能形成更长的标识符。这不是非常复杂,但也不是微不足道的;您需要做一些工作来创建适当的测试覆盖率。

随着可能的扩展前瞻状态的数量增加,您会发现此解决方案越来越复杂。 (并不是说上面概述的解析器解决方案也很棒。)

2。使用GLR解析器

由于语法本身并不含糊,因此如果您有一个可以生成GLR解析器的解析器生成器,则可以使用GLR解析器而不是LR(1)解析器。 GLR算法可以解析任何无上下文语法,但代价是:它在常见情况下略慢,在边缘情况下慢得多(在某些语法中,它可能需要二次或甚至三次),典型实现会延迟语义操作直到歧义得到解决,这意味着他们不一定按照预期的顺序执行。

在这种情况下,对此解决方案有更强烈的抑制作用:PLY 不会产生GLR语法。但是,bison确实有一个GLR选项,将bison解析器包装到python模块中相对简单(如果重要的话,这很可能比PLY解析器更快)。

3。使用堆栈而不是CFG

PDF源自Postscript,它是一种堆栈语言。堆栈语言通常不需要解析器;他们只有一个词法扫描器和一个堆栈。每个令牌都有一个相关的动作;对于许多令牌(例如文字),操作只是将令牌推送到堆栈上。其他令牌可能会有更复杂的行为。

例如,]令牌通过弹出堆栈创建一个数组,直到找到[并将弹出的元素放入新创建的数组对象中,然后将其推入堆栈

同样,R令牌的动作是从堆栈顶部弹出两个东西;检查它们是否是数字;然后使用这两个数字构造一个间接引用对象,然后将其推入堆栈。

PLY在构建PDF堆栈解析器方面不会给你太多帮助,但它至少会为你构建词法分析器,这实际上是解析器中最复杂的部分。所需的堆栈操作并不多,而且没有一个特别具有挑战性。