динамически генерировать регулярные выражения из ключей словаря python - PullRequest
0 голосов
/ 06 января 2019
def t_FUNC_(self, t):
        r'(?i)I|(?i)J|(?i)K|(?i)L|(?i)M|(?i)N|(?i)Y'
        return t

В приведенной выше функции я возвращаю регулярное выражение, что означает, что FUNC может быть I или J или K или L или M или N или Y.

Теперь у меня есть словарь:

dic = { 'k1':'v1', 'k2':'v2' }

У меня есть доступ к этому словарю в функции выше. Как мне динамически генерировать регулярное выражение из ключей словаря. Размер словаря также не фиксирован.

Итак, я хочу заменить r'(?i)I|(?i)J|(?i)K|(?i)L|(?i)M|(?i)N|(?i)Y' на что-то вроде r'(?i)k1|(?i)k2.

PS: вышеуказанный код шаблона используется для генерации токенов, когда мы пишем наш лексер, используя библиотеку ply в python.

Ответы [ 3 ]

0 голосов
/ 06 января 2019
'(?i)'+'|'.join(re.escape(k) for k in dic)

Вам нужен re.escape в случае, если одна из клавиш dic содержит управляющий символ на языке регулярных выражений (например, |). Кроме того, использование глобальных встроенных флагов, таких как (?i), не рекомендуется в любом месте шаблона, кроме начала. (Если вы хотите применить его только к части выражения, вы можете использовать новый синтаксис локального флага, (?i:foo).)

0 голосов
/ 06 января 2019

Как говорит @ AustinHastings в комментарии, Ply создает лексический сканер, комбинируя регулярные выражения, предоставленные в классе лексера, либо в качестве значений членов класса, либо в виде строк документации функций-членов класса. После того, как сканер собран, он не будет изменен, поэтому вы действительно не сможете динамически корректировать регулярные выражения, по крайней мере, после того, как сканер был сгенерирован.

Однако для конкретного приложения, которое вы имеете в виду, нет необходимости создавать пользовательское регулярное выражение. Вы можете использовать гораздо более простую процедуру, показанную в руководстве Ply , которое показывает, как распознавать зарезервированные слова без специального регулярного выражения для каждого слова.

Идея действительно проста. Зарезервированные слова - имена функций в вашем случае - как правило, являются конкретными примерами некоторого более общего паттерна, уже используемого в лексическом сканере. Это почти наверняка так, потому что лексический сканер должен каким-то образом распознавать каждый токен, поэтому, прежде чем динамически генерируемое слово будет добавлено в сканер, оно должно быть распознано как что-то еще. Вместо того чтобы пытаться переопределить этот другой шаблон для конкретного экземпляра, мы просто позволяем токену распознать и затем исправить его тип (и, возможно, его значение) перед возвратом токена.

Вот слегка измененная версия примера из руководства Ply:

def t_ID(t):
     r'[a-zA-Z_][a-zA-Z_0-9]*'
     # Apparently case insensitive recognition is desired, so we use
     # the lower-case version of the token as a lookup key. This means
     # that all the keys in the dictionary must be in lower-case
     token = t.value.lower()
     if token in self.funcs:
         t.type = 'FUNC'
     return t

(Возможно, вы захотите откорректировать вышеприведенное так, чтобы оно что-то делало со значением, связанным с ключом в словаре funcs, хотя это можно было бы сделать позже во время семантического анализа.)

Поскольку словарь funcs никоим образом не участвует в генерации лексера (или синтаксического анализатора), для его передачи в объект Lexer не требуется особый ум. На самом деле, он даже не должен находиться в объекте lexer; Вы можете добавить объект парсера к объекту лексера при создании объекта лексера, что позволяет поместить словарь в объект парсера, где он более доступен для действий парсера.

Одна из причин того, что это гораздо лучшее решение, чем попытка создания настраиваемого регулярного выражения, заключается в том, что оно не распознает зарезервированные слова, которые могут быть найдены как префиксы незарезервированных слов. Например, если cos была одной из функций, и вам удалось получить эквивалент

t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'
def t_FUNC(t):
    r'(?i)sin|cos|tan'
    # do something

тогда вы обнаружите, что:

cost = 3

был отсканирован как FUNC(cos), ID(t), '=', NUMBER(3), что почти наверняка не то, что вы хотите. Размещение логики внутри функции t_ID полностью исключает эту проблему, поскольку будут рассматриваться только полные токены.

0 голосов
/ 06 января 2019

Поместить ключи dict в ваше регулярное выражение так же просто, как:

Код:

regex = '|'.join('(?i){}'.format(k) for k in data)

Тестовый код:

data = {'k1': 'v1', 'k2': 'v2'}
regex = '|'.join('(?i){}'.format(k) for k in data)
print(regex)

Результаты:

(?i)k1|(?i)k2
...