Как исключить определенные возможности из регулярного выражения?

Для парсера, который я создаю, я использую это регулярное выражение в качестве определения идентификатора:

ID: /[a-z_][a-z0-9]*/i

(Для тех, кто не знаком с синтаксисом конкретного парсера, который я использую, флаг "i" просто означает без учета регистра.)

У меня также есть ряд ключевых слов, например:

CALL_KW: "call"
PRINT_KW: "print"

Проблема в том, что из-за некоторых двусмысленностей в грамматике иногда ключевые слова рассматриваются как идентификаторы, в то время как я действительно не хочу, чтобы они были. Поэтому я подумал, можно ли переписать регулярное выражение для идентификатора таким образом, чтобы ключевые слова вообще не сопоставлялись с ним. Это возможно?

Чтобы дать больше контекста, я использую библиотеку синтаксического анализатора Lark для Python. Парсер Earley, который обеспечивает Lark (вместе с динамическим лексером), достаточно гибок и силен в обработке неоднозначных грамматик, но иногда они делают странные вещи, подобные этой (и недетерминированно, в этом!). Итак, я пытаюсь помочь парсеру, делая ключевые слова никогда не совпадающими с правилом идентификатора.

2 ответа

Решение

Я полагаю, что Ларк использует обычные регулярные выражения Python, поэтому вы можете использовать отрицательное косвенное утверждение, чтобы исключить ключевые слова. Но вы должны позаботиться о том, чтобы не отклонять имена, начинающиеся с ключевого слова:

ID: /(?!(else|call)\b)[a-z_][a-z0-9]*/i

Это регулярное выражение, безусловно, работает в Python3:

>>> # Test with just the word
>>> for test_string in ["x", "xelse", "elsex", "else"]:
...   m = re.match(r"(?!(else|call)\b)[a-z_][a-z0-9]*", test_string)
...   if m: print("%s: Matched %s" % (test_string, m.group(0)))
...   else: print("%s: No match" % test_string)
... 
x: Matched x
xelse: Matched xelse
elsex: Matched elsex
else: No match

>>> # Test with the word as the first word in a string
... for test_string in [word + " and more stuff" for word in ["x", "xelse", "elsex", "else"]]:
...   m = re.match(r"(?!(else|call)\b)[a-z_][a-z0-9]*", test_string)
...   if m: print("%s: Matched %s" % (test_string, m.group(0)))
...   else: print("%s: No match" % test_string)
... 
x and more stuff: Matched x
xelse and more stuff: Matched xelse
elsex and more stuff: Matched elsex
else and more stuff: No match

Есть несколько способов не передавать аналогичные значения вашим идентификаторам.

RegEx 1

Например, вы можете использовать группы захвата в своем выражении, может быть что-то похожее на

    ([a-z]+_[a-z0-9]+)

RegEx Circuit

Эта ссылка поможет вам визуализировать ваши выражения:

RegEx 2

Другим способом было бы связать ваше выражение справа, используя :, тогда вы можете использовать выражение, подобное:

(\w+):

или ваше оригинальное выражение с i флаг:

([a-z0-9_]+):

Вы можете добавить больше границ, если хотите.

Другие вопросы по тегам