python pyparsing критерии несоответствия (ключевого слова) для Word

Я пытаюсь создать синтаксический анализатор, который анализирует различные виды выражений, состоящих из строк Verilog и строк в кавычках. Чтобы заставить это работать, я использую конструкцию MatchFirst. Одна икота, с которой я сталкиваюсь, заключается в том, что я не знаю, как создать слово, которое не соответствует, если за ним следуют определенные символы.

Краткая версия проблемы

Предположим, мне нужно слово, которое может принимать символы «А» и «В», но не в том случае, если за ними следует любая другая буква. Таким образом, они должны совпадать:

A
AB
BA
BAABBABABABA

Но это не должно совпадать: BABC

В настоящее время синтаксический анализатор в конечном итоге частично совпадает, что искажает результат.

Длинная версия проблемы

Этот вопрос связан с предыдущим вопросом, который я задал: python pyparsing ^ vs | ключевые слова

Ниже приведен тестовый пример python3, иллюстрирующий проблему. ПРИМЕЧАНИЕ. Если бы мне пришлось изменить синтаксический анализатор с использования конструкции MatchFirst на ИЛИ, тестовый пример прошел бы успешно. т.е. 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

Итак, здесь тестовая строка интерпретируется как число Verilog 'HA, равное 10, вместо строки в кавычках: 'HARDWARE'

Я пробовал возиться с аргументом ключевого слова asKeyword, но мне не повезло с этим.

РЕДАКТИРОВАТЬ

Основываясь на помощи Пола, я добавил дополнительные проверки в тестовый пример для дальнейшего уточнения решения. Я воспользовался предложением Пола добавить 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

Логично попробовать добавить asKeyword=True для определения dec_num. Что я и сделал, но это приводит к странной ошибке:

  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, похоже, также испортит синтаксический анализ чисел, в отличие от строк в кавычках.


person Gregory Kuhn    schedule 02.01.2016    source источник


Ответы (1)


Аргумент asKeyword для Word заключает внутреннее регулярное выражение в скобки с помощью '\b'. Я думаю, что ваше добавление аргумента excludeChars все портит. Просто определите hex_num как:

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

Когда я запускаю ваш тестовый код, это работает. (Я думаю, что hexnums — единственное из трех числовых значений, которые требуют этого, поскольку десятичные и двоичные числа не имеют никакой двусмысленности с завершающими буквенными символами.)

К сведению: excludeChars добавлено в Word, чтобы упростить определение групп символов «всего в печатных формах, кроме ':'», или «всего в буквах, кроме '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 для числового поля регулярного выражения. Я также добавил вызовы setName, которые теперь немного важнее, поскольку Or и MatchFirst (правильно) перечисляют все опции в своих сообщениях об исключениях.

РЕДАКТИРОВАТЬ(2)

Только что заметил, что мы терпят неудачу на 'HA', я думаю, что это будет решено, если вы просто измените порядок альтернатив вашего парсера:

parser = pp.quotedString | (_get_verilog_num_parse() ^ pp.Literal("Some_demo_literal"))
person PaulMcG    schedule 02.01.2016
comment
Спасибо за хорошее предложение. Я бы пометил это как решенное, но я несколько изменил цель и дополнительно уточнил свой вопрос с некоторыми дополнительными проверками, чтобы проверить определение dec_num. Короче говоря, почему я не могу использовать это решение для dec_num? Я более подробно остановился на своем вопросе выше. - person Gregory Kuhn; 03.01.2016
comment
Привет, Пол, я не знаю, где ты находишь время, чтобы ответить на мои вопросы, но большое спасибо, это очень ценно. Это решение, безусловно, работает в тестовом примере, который я предоставил. Я попробую это в ближайшее время в моем более сложном парсере и надеюсь на лучшее. Простите, что говорю так, но согласитесь ли вы, что это обходной путь? Почему мы не могли использовать функциональность asKeyword? Я не хочу быть педантичным, но я уверен, что столкнусь с этим снова, и я не хочу полагаться на приоритет для решения проблемы. Есть ли шанс, что я могу заставить вас уточнить это, пожалуйста? - person Gregory Kuhn; 03.01.2016
comment
asKeyword заключает в скобки внутреннее регулярное выражение Word с \b' before and after. But in 'HA' there is no word break between the H and A, so no match. I don't think you had any test case for just 'HA` - используя только asKeyword, это не удается. Я почти уверен, что в моем синтаксическом анализаторе Verilog, включенном в исходный дистрибутив pyparsing, я определил dec_num и т. д., используя некоторые подобные конструкции. Если нет, я обязательно добавлю в него эти выражения. - person PaulMcG; 03.01.2016
comment
У меня все еще были реальные проблемы с интеграцией этого в мой готовый парсер. Это происходит из-за неоднозначности поиска строки в кавычках или числа Verilog в таких выражениях: "='blah' == 'erm'?'d1:'d1+2" Я решил эту проблему, установив сначала приоритет для чисел Verilog. Чтобы заставить это работать, я изменил вашу лямбду make_num_expr на следующее: make_num_expr = lambda prefix,numeric_chars,radix: pp.Regex(r"[%s%s](?P<num>[%s_]+)(?![\w'])" % . В основном это означает, что регулярное выражение не будет совпадать, если символ, нарушивший слово, является 'Теперь я счастливый турист! - person Gregory Kuhn; 04.01.2016