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

python pyparsing non match (keyword) criteria for Word

我正在尝试创建一个解析器来解析由 verilog 字符串和带引号的字符串组成的不同类型的表达式。为了让它工作,我使用了 MatchFirst 结构。我遇到的一个小问题是我不知道如何创建一个如果后跟某些字符则不匹配的单词。

问题的简版

假设我想要一个可以接受字符 'A' 和 'B' 的 Word,但如果它们后跟任何其他字母则不能。 所以这些应该匹配:

A
AB
BA
BAABBABABABA

但这不应该匹配:BABC

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

长版问题

这个问题与我之前问过的一个问题有关:

下面是说明问题的 python3 测试用例。 注意 如果我必须将解析器从使用 MatchFirst 构造更改为 OR,则测试用例通过。 IE。 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 的 "longest" 匹配选择性的情况下让这个匹配工作?

测试用例

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:
    
    """
    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

所以在这里,测试字符串被解释为 verilog 数字 'HA,即 10 而不是带引号的字符串:'HARDWARE'

我试过弄乱 asKeyword 关键字参数,但我没有任何运气。

编辑

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

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

然后我又添加了 2 个检查:

("'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 似乎也搞乱了数字的解析,而不是引用的字符串。

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 中以简化 "everything in printables except ':'" 或 "everything in alphanums except 'Q'" 字符组的定义。 (https://pythonhosted.org/pyparsing/pyparsing.Word-class.html)

编辑

我认为部分问题在于我们需要同时查看单个表达式中的前缀 h/d/b 字符和数字字符,以便正确处理数字字符。我们想在数字之后强制中断,而不是在前导前缀和数字之间。恐怕最好的方法是求助于正则表达式。这是一组将前缀和数字组合成等效正则表达式的表达式,并添加了 trailing-but-not-leading 分词符:

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。我还添加了 setName 调用,现在 Or 和 MatchFirst(正确地)枚举了它们的异常消息中的所有选项。

编辑(2)

刚刚注意到我们在 'HA' 上失败了,我认为如果您只更改解析器替代项的顺序,这个问题就会得到解决:

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