Поиск ключевых слов в Pyparsing:нежадное поглощение токенов
Вопрос
Питонисты:
Предположим, вы хотите разобрать следующую строку с помощью Pyparsing:
'ABC_123_SPEED_X 123'
были ABC_123
является идентификатором; SPEED_X
является параметром, и 123
это ценность.Я подумал о следующем BNF, используя Pyparsing:
Identifier = Word( alphanums + '_' )
Parameter = Keyword('SPEED_X') or Keyword('SPEED_Y') or Keyword('SPEED_Z')
Value = # assume I already have an expression valid for any value
Entry = Identifier + Literal('_') + Parameter + Value
tokens = Entry.parseString('ABC_123_SPEED_X 123')
#Error: pyparsing.ParseException: Expected "_" (at char 16), (line:1, col:17)
Если я уберу подчеркивание посередине (и отрегулирую Entry
определение соответственно) он анализируется корректно.
Как я могу сделать этот анализатор немного более ленивым и подождать, пока он не совпадет с ключевым словом (в отличие от использования всей строки в качестве идентификатора и ожидания _
, которого не существует.
Спасибо.
[Примечание:Это полная перепись моего вопроса;Я не понимал, в чем заключалась настоящая проблема]
Решение
Я основал свой ответ на этот, поскольку то, что вы пытаетесь сделать, - это получить нежадное совпадение.Кажется, что это трудно реализовать в pyparsing, но не невозможно при некоторой сообразительности и компромиссе.Кажется, работает следующее:
from pyparsing import *
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
UndParam = Suppress('_') + Parameter
Identifier = SkipTo(UndParam)
Value = Word(nums)
Entry = Identifier + UndParam + Value
Когда мы запускаем это из интерактивного интерпретатора, мы можем увидеть следующее:
>>> Entry.parseString('ABC_123_SPEED_X 123')
(['ABC_123', 'SPEED_X', '123'], {})
Обратите внимание, что это компромисс;потому что я использую SkipTo
, тот самый Identifier
может быть полно злых, отвратительных персонажей, а не просто красивых alphanums
со случайным подчеркиванием.
Редактировать: Благодаря Полу Макгуайру мы можем придумать по-настоящему элегантное решение, установив Identifier
к следующему:
Identifier = Combine(Word(alphanums) +
ZeroOrMore('_' + ~Parameter + Word(alphanums)))
Давайте посмотрим, как это работает.Во-первых, игнорируйте внешнее Combine
;мы вернемся к этому позже.Начиная с Word(alphanums)
мы знаем, что получим 'ABC'
часть ссылочной строки, 'ABC_123_SPEED_X 123'
.Важно отметить, что в данном случае мы не разрешили "слову" содержать подчеркивания.Мы встраиваем это отдельно в логику.
Далее, нам нужно зафиксировать '_123'
расстаться, не всасывая при этом '_SPEED_X'
.Давайте также перейдем к следующему ZeroOrMore
остановитесь на этом моменте и вернитесь к нему позже.Мы начинаем с подчеркивания в виде Literal
, но мы можем сократить путь с помощью просто '_'
, что даст нам начальное подчеркивание, но не все '_123'
.Инстинктивно мы бы разместили еще один Word(alphanums)
чтобы захватить остальное, но это именно то, что доставит нам неприятности, если мы потребим все оставшееся '_123_SPEED_X'
.Вместо этого мы говорим: "При условии, что то, что следует за подчеркиванием, является нет тот самый Parameter
, проанализируйте это как часть моего Identifier
.Мы утверждаем, что в терминах pyparsing как '_' + ~Parameter + Word(alphanums)
.Поскольку мы предполагаем, что у нас может быть произвольное количество повторений подчеркивания + WordButNotParameter, мы оборачиваем это выражение a ZeroOrMore
сконструировать.(Если вы всегда ожидаете, что после инициала следует хотя бы символ подчеркивания + WordButNotParameter, вы можете использовать OneOrMore
.)
Наконец, нам нужно обернуть начальное слово и специальное подчеркивание + Слово, повторяющееся вместе, чтобы было понятно, что они смежные, не разделенные пробелом, поэтому мы оборачиваем все выражение в Combine
сконструировать.Сюда 'ABC _123_SPEED_X'
вызовет ошибку синтаксического анализа, но 'ABC_123_SPEED_X'
будет правильно проанализирован.
Обратите также внимание, что мне пришлось изменить Keyword
Для Literal
потому что способы первых слишком утонченны и быстры, чтобы вызвать гнев.Я не доверяю Keyword
s, и я также не мог найти с ними общий язык.
Другие советы
Если вы уверены, что идентификатор никогда не заканчивается символом подчеркивания, вы можете применить его в определении:
from pyparsing import *
my_string = 'ABC_123_SPEED_X 123'
Identifier = Combine(Word(alphanums) + Literal('_') + Word(alphanums))
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z')
Value = Word(nums)
Entry = Identifier + Literal('_').suppress() + Parameter + Value
tokens = Entry.parseString(my_string)
print tokens # prints: ['ABC_123', 'SPEED_X', '123']
Если это не так, но если длина идентификатора фиксирована, вы можете определить идентификатор следующим образом:
Identifier = Word( alphanums + '_' , exact=7)
Вы также можете проанализировать идентификатор и параметр как один токен и разделить их в действии синтаксического анализа:
from pyparsing import *
import re
def split_ident_and_param(tokens):
mo = re.match(r"^(.*?_.*?)_(.*?_.*?)$", tokens[0])
return [mo.group(1), mo.group(2)]
ident_and_param = Word(alphanums + "_").setParseAction(split_ident_and_param)
value = Word(nums)
entry = ident_and_param + value
print entry.parseString("APC_123_SPEED_X 123")
В приведенном выше примере предполагается, что идентификаторы и параметры всегда имеют формат XXX_YYY (содержащий один-единственный символ подчеркивания).
Если это не так, вам необходимо скорректировать split_ident_and_param()
способ.
Это ответ на вопрос, который вы, вероятно, тоже задавали себе:"Для чего нужно реальное приложение reduce
?):
>>> keys = ['CAT', 'DOG', 'HORSE', 'DEER', 'RHINOCEROS']
>>> p = reduce(lambda x, y: x | y, [Keyword(x) for x in keys])
>>> p
{{{{"CAT" | "DOG"} | "HORSE"} | "DEER"} | "RHINOCEROS"}
Редактировать:
Это был довольно хороший ответ на первоначальный вопрос.Мне придется поработать над новым.
Дальнейшее редактирование:
Я почти уверен, что ты не сможешь сделать то, что пытаешься сделать.Синтаксический анализатор, который pyparsing
creates не заглядывает вперед.Так что, если вы скажете, что это соответствует Word(alphanums + '_')
, он будет продолжать сопоставлять символы до тех пор, пока не найдет тот, который не является буквой, цифрой или подчеркиванием.