python pyparsing Word的非匹配(关键字)条件

时间:2016-01-02 11:22:11

标签: python pyparsing

我试图创建一个解析器,它解析由verilog字符串和带引号的字符串组成的不同类型的表达式。为了使其工作,我使用MatchFirst构造。我遇到的一个打嗝是我不知道如何创建一个如果后面跟着某些字符不匹配的单词。

问题的简短版本

我们假设我想要一个可以接受角色的单词' A'和' B'但如果跟随其他任何信件,则不会。 所以这些应该匹配:

A
AB
BA
BAABBABABABA

但这不应该匹配:BABC

Currenly,解析器最终部分匹配,这会弄乱结果。

问题的长版本

此问题与我之前提出的问题有关:python pyparsing "^" vs "|" keywords

下面是一个说明问题的python3测试用例。 注意如果我必须将解析器从使用MatchFirst构造更改为OR,则测试用例会通过。 即parser = (_get_verilog_num_parse() ^ pp.Literal("Some_demo_literal")) ^ pp.quotedString而不是parser = (_get_verilog_num_parse() ^ pp.Literal("Some_demo_literal")) | pp.quotedString 但是,这又构成了一个更复杂的解析器的一部分,而且(我认为)我需要优先级才能让它工作。

所以最终,问题是如何在不依赖于OR"最长的"匹配选择性?

测试用例

import unittest
import pyparsing as pp

def _get_verilog_num_parse():
    """Get a parser that can read a verilog number
    return: Parser for verilog numbers
    rtype: PyParsing parser object

    See this link where I got help with geting this parser to work:
    https://stackoverflow.com/questions/34258011/python-pyparsing-vs-keywords
    """
    apos           = pp.Suppress(pp.Literal("'"))
    size_num        = pp.Word(pp.nums+'_'                  ).setParseAction(lambda x:int(x[0].replace('_', ''),10))
    #dec_num        = pp.Word(pp.nums+'_'   , asKeyword=True).setParseAction(lambda x:int(x[0].replace('_', ''),10))
    dec_num        = pp.Word(pp.nums+'_'                   ).setParseAction(lambda x:int(x[0].replace('_', ''),10))
    hex_num        = pp.Word(pp.hexnums+'_', asKeyword=True).setParseAction(lambda x:int(x[0].replace('_', ''),16))
    bin_num        = pp.Word('01'+'_',       asKeyword=True).setParseAction(lambda x:int(x[0].replace('_', ''),2))

    size           = pp.Optional(size_num).setResultsName('size')


    def size_mask(parser):
        size = parser.get('size')
        if size is not None:
            return parser['value'] & ((1<<size) -1)
        else:
            return parser['value']

    radix_int = pp.ungroup(pp.CaselessLiteral('d').suppress() + dec_num |
                           pp.CaselessLiteral('h').suppress() + hex_num |
                           pp.CaselessLiteral('b').suppress() + bin_num)
    #print(radix_int)
    return (size + apos + radix_int('value')).addParseAction(size_mask)

class test_PyParsing(unittest.TestCase):
    '''Check that the Expression Parser works with the expressions
    defined in this test'''

    def test_or(self):
        """Check basic expressions not involving referenced parameters"""
        expressions_to_test = [
                ("8'd255",255),
                ("'d255",255),
                ("12'h200",0x200),
                ("'blah'","'blah'"),
                ("'HARDWARE'","'HARDWARE'"),
                ("'HA'","'HA'"),
                ("'b101010'","'b101010'"),
                ("'d1010'","'d1010'"),
                ("'1010'","'1010'"),
                ]
        parser = (_get_verilog_num_parse() ^ pp.Literal("Some_demo_literal")) | pp.quotedString
        for expr,expected in expressions_to_test:
            result = parser.parseString(expr)
            #print("result: {}, val: {}".format(result, result[0]))
            self.assertEqual(expected,result[0], "test_string: {}, expected: {}, result: {}".format(expr, expected, result[0]))

结果

self.assertEqual(expected,result[0], "test_string: {}, expected: {}, result: {}".format(expr, expected, result[0]))
AssertionError: "'HARDWARE'" != 10 : test_string: 'HARDWARE', expected: 'HARDWARE', result: 10

所以在这里,teststring被解释为一个verilog数'HA,它是10而不是带引号的字符串:'HARDWARE'

我已经尝试过使用asKeyword关键字参数,但我对此没有任何好运。

修改

基于Paul的帮助到目前为止,我在测试用例中添加了额外的检查以进一步完善解决方案。 我已经使用了Paul的建议,将asKeyword=True添加到了hex_num的定义中,这解决了我原来的问题,然后将其添加到bin_num的定义中,以满足添加的检查:

("'b101010'","'b101010'"),
("'d1010'","'d1010'"),

然后又添加了两张支票:

("'d1010'","'d1010'"),
("'1010'","'1010'"),

然后使测试用例失败,结果如下:

self.assertEqual(expected,result[0], "test_string: {}, expected: {}, result: {}".format(expr, expected, result[0]))
AssertionError: "'d1010'" != 1010 : test_string: 'd1010', expected: 'd1010', result: 1010

尝试的逻辑是然后为dec_num的定义添加asKeyword=True。我做了但这导致了一个奇怪的错误:

  result = parser.parseString(expr)
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 1125, in parseString
  raise exc
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 1115, in parseString
  loc, tokens = self._parse( instring, 0 )
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 989, in _parseNoCache
  loc,tokens = self.parseImpl( instring, preloc, doActions )
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 2497, in parseImpl
  raise maxException
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 2483, in parseImpl
  ret = e._parse( instring, loc, doActions )
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 989, in _parseNoCache
  loc,tokens = self.parseImpl( instring, preloc, doActions )
File "c:\users\gkuhn\appdata\local\continuum\anaconda3\lib\site-packages\pyparsing.py", line 2440, in parseImpl
  raise maxException
pyparsing.ParseException: Expected W:(0123...) (at char 3), (line:1, col:4)

注意

添加asKeyword=True似乎也破坏了数字的解析而不是引用的字符串。

1 个答案:

答案 0 :(得分:0)

Word的asKeyword参数用'\b'括起内部正则表达式。我认为你添加excludeChars论点是搞乱的。只需将hex_num定义为:

hex_num = pp.Word(pp.hexnums+'_', asKeyword=True).setParseAction(
                                                  lambda x:int(x[0].replace('_', ''),16))

当我运行您的测试代码时,这是有效的。 (我认为hexnums是需要这个的3个数字中唯一的一个,因为十进制和二进制对于尾随字母字符没有任何歧义。)

仅供参考 - excludeChars被添加到Word中以简化“除了':'”之外的所有“printables中的所有内容”或“除'Q'之外的所有字母”中的所有字符组。 (https://pythonhosted.org/pyparsing/pyparsing.Word-class.html

修改

我认为问题的一部分是我们需要在单个表达式中查看前缀h / d / b字符和数字字符,以便使用数字字符做正确的事情。我们希望在数字之后强制执行中断,但不要在前导前缀和数字之间执行。我担心最好的方法是使用正则表达式。这是一组表达式,它们将前缀和数字组合成一个等效的正则表达式,并添加了尾随但不引导的单词中断:

make_num_expr = lambda prefix,numeric_chars,radix: pp.Regex(r"[%s%s](?P<num>[%s_]+)\b" % 
                                                                (prefix,prefix.upper(),numeric_chars)).setParseAction(
                                                                        lambda x: int(x.num.replace('_',''), radix))
dec_num = make_num_expr('d', pp.nums, 10).setName("dec_num")
hex_num = make_num_expr('h', pp.hexnums, 16).setName("hex_num")
bin_num = make_num_expr('b', '01', 2).setName("bin_num")

radix_int = (dec_num | hex_num | bin_num).setName("radix_int")

请注意使用命名组num作为Regex的数字字段。我还添加了setName个调用,现在Or和MatchFirst(正确地)枚举了他们的异常消息中的所有选项,这些调用更为重要。

EDIT(2)

注意到我们在'HA'上失败了,如果您只是更改解析器备选方案的顺序,我认为这会得到解决:

parser = pp.quotedString | (_get_verilog_num_parse() ^ pp.Literal("Some_demo_literal"))