Пипарсинг — возвращение к основам

Пытаясь собрать очень простой пример, чтобы проиллюстрировать проблему, с которой я столкнулся при анализе pyparsing, я обнаружил, что не могу заставить свой простой пример работать — этот пример едва ли сложнее, чем Hello, World!

Вот мой пример кода:

import pyparsing as pp
import textwrap as tw

text = (
    """
    A) Red
    B) Green
    C) Blue
    """
)

a = pp.AtLineStart("A)") + pp.Word(pp.alphas)
b = pp.AtLineStart("B)") + pp.Word(pp.alphas)
c = pp.AtLineStart("C)") + pp.Word(pp.alphas)

grammar = a + b + c

grammar.run_tests(tw.dedent(text).strip())

Я ожидал, что он вернется ["A)", "Red", "B)", "Green", "C)", "Blue"], но вместо этого получаю:

A) Red
A) Red
      ^
ParseException: not found at line start, found end of text  (at char 6), (line:1, col:7)
FAIL: not found at line start, found end of text  (at char 6), (line:1, col:7)

B) Green
B) Green
^
ParseException: Expected 'A)', found 'B'  (at char 0), (line:1, col:1) 
FAIL: Expected 'A)', found 'B'  (at char 0), (line:1, col:1)

C) Blue
C) Blue
^
ParseException: Expected 'A)', found 'C'  (at char 0), (line:1, col:1)
FAIL: Expected 'A)', found 'C'  (at char 0), (line:1, col:1)

Почему после первой строки написано, что найден конец текста??? Почему он ожидает А) после первой строки???

(Примечание: textwrap.dedent() и strip() не влияют на результаты этого скрипта.)

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Мой друг! Вы забыли обернуть необработанную строку в list!

Я тестировал 30 минут и почувствовал, что что-то не так, а затем нашел это в документе:

run_tests(тесты: Union[str, List[str]], ...) -> ...

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

Параметры:

тесты - список отдельных тестовых строк или многострочная строка тестовых строк.

По сути, вы (и я за последние полчаса) делаете это:

grammar.run_tests(tw.dedent(text).strip())

...Я говорил ему рассматривать каждую строку как отдельные тесты!

"""  # You are a test 0 now!
    A) Red  # You are a test 1
    B) Green  # test 2 for you,
    C) Blue  # test 3 for ya,
""" # finally you're test 4! Mhahahah!

(И, конечно же, использование pp.line_end, чтобы линия действительно потребляла end of line)

>>> import pyparsing as pp
... import textwrap as tw
...
... text = (
...     """
...     A) Red
...     B) Green
...     C) Blue
...     """
... )
...
... a = pp.AtLineStart("A)") + pp.Word(pp.alphas) + pp.line_end.suppress()
... b = pp.AtLineStart("B)") + pp.Word(pp.alphas) + pp.line_end.suppress()
... c = pp.AtLineStart("C)") + pp.Word(pp.alphas)
...
... grammar = a + b + c
... grammar.run_tests([tw.dedent(text).strip()])


A) Red
B) Green
C) Blue
['A)', 'Red', 'B)', 'Green', 'C)', 'Blue']
>>>

Спасибо за ваш ответ @jupiterbjy! Я действительно хочу +, поскольку текст, который я анализирую, имеет шесть токенов, а моя грамматика состоит из шести элементов, каждый из которых должен соответствовать одному токену. Использование |, как вы это сделали, будет соответствовать A) Red\nA) Green\nA) Blue, но это недопустимый вход для моего парсера — ключевые слова A), B) и C) значимы и должны появляться по порядку.

Michael Henry 02.07.2024 10:40

Я забыл сказать: функции textwrap.dedent() и strip() предназначены для отражения сценария, который я пытаюсь отладить; Мне кажется, что pyparsing неправильно обрабатывает пробелы (несмотря на то, что в документации говорится, что он игнорирует пробелы!). В этом случае необходим textwrap.dedent(), потому что AtLineStart() не будет соответствовать моим ключевым словам, если они имеют отступ - я подтвердил, что входные данные, которые я пытаюсь проанализировать, фактически содержат ключевые слова сразу после символа новой строки.

Michael Henry 02.07.2024 10:49

@MichaelHenry Интересно, есть ли в этом источнике какой-нибудь скрытый возврат каретки или какие-нибудь вкладки... интересно. Можете ли вы обновить ответ, включив в него и эти детали?

jupiterbjy 02.07.2024 13:06

Огромное спасибо @jupiterbjy! Исключительная работа! Когда я это опубликовал, я знал, что мне будет неловко. Все сводится к отсутствию у меня навыков Python;) Я заключил тестовый текст в круглые скобки, потому что, по моему мнению, именно так вы создаете список (спасибо, lisp!). Для меня квадратные скобки предназначены для массивов (я все еще люблю тебя, C), что и было основной причиной моей проблемы.

Michael Henry 03.07.2024 01:19

@MichaelHenry о, ты можешь сделать то же самое в кортеже! Но поскольку в Python (и некоторых других языках) используется (), чтобы разрешить перемещение R-значения от L-значения в случае, если L-значение становится слишком длинным или т. д., это неотличимо от определения кортежа с одним значением, поэтому мы используем (val,), чтобы указать, что это действительно кортеж с одним значением (эта конечная запятая). Кстати, я до сих пор помню, как профессор дал мне аппаратное обеспечение Lisp и Prolog для реализации пузырьковой сортировки и n-queen, замечательные люди могут быть достаточно умными, чтобы использовать это с моей точки зрения!

jupiterbjy 03.07.2024 02:15

Да, и поскольку в Python list похожи на std::vector<std::variant> не связанный список, так что... каким-то образом это может быть массив, по крайней мере, внутри? Хотя «настоящие массивы» в Python выглядят немного иначе, например import array \ array.array("typecode", Iterable), но в основном инициализируются списками типа array.array("i", [1, 2, 3]) — это может быть полезно для запоминания!

jupiterbjy 03.07.2024 02:29

Спасибо за вашу настойчивость в изучении pyparsing! В документации AtLineStart определенно есть ошибка, которая вам мешает, и я исправлю ее сегодня днём или вечером.

Прежде всего, вот код из документации AtLineStart:

import pyparsing as pp

# from the docstring for AtLineStart
test = '''\
AAA this line
AAA and this line
  AAA but not this one
B AAA and definitely not this one
'''

for t in (pp.AtLineStart('AAA') + pp.rest_of_line).search_string(test):
    print(t)

Почти как ваш пример, но с некоторыми важными отличиями. Этот код работает, потому что он использует search_string. AtLineStart не пропускает пробелы — они совпадают ТОЛЬКО в начале строки. Оглядываясь назад, можно сказать, что использование search_string было неудачным, поскольку оно маскирует строгое соответствие начала строки, которое обеспечивает AtLineStart.

Проблема в вашем примере заключается в том, что после анализа Word(pp.alphas) после "A)" синтаксический анализатор позиционируется в \n в конце строки - AtLineStart ТОЛЬКО соответствует, если он позиционируется на символе, который является первым символом во всем вводе. строка или первый символ после новой строки.

Мы исправляем это, явно анализируя новую строку:

# You'll see the \n's in the output - suppress this if you don't like them
NL = pp.LineEnd()
a = pp.AtLineStart("A)") + pp.Word(pp.alphas) + NL
b = pp.AtLineStart("B)") + pp.Word(pp.alphas) + NL
c = pp.AtLineStart("C)") + pp.Word(pp.alphas) + NL

grammar = a + b + c

Как услужливо заметил @jupiterbjy, когда run_tests дается одна строка, он обрабатывает ее как doctest, при этом каждая строка является отдельным тестом (на самом деле вы также можете перемежать пустые строки и комментарии '#', чтобы аннотировать выходные данные теста) . Чтобы заставить run_tests работать с многострочным тестом, сделайте его одной строкой с \n или заключите строку в тройных кавычках в [], чтобы явно создать свой собственный список тестов - тогда этот список может содержать несколько многострочных тестов. струны.

Вот код, который я использовал, чтобы попытаться проанализировать ваш ввод и показать исключение:

try:
    result = grammar.parse_string(textwrap.dedent(text).strip())
except ParseException as pe:
    print(pp.testing.with_line_numbers(textwrap.dedent(text).strip(), eol_mark = "<"))
    print(pe.explain())
else:
    print(result.dump())

С вашей исходной грамматикой это печатает:

            1
   1234567890
 1:A) Red<
 2:B) Green<
 3:C) Blue<

A) Red
      ^
ParseException: not found at line start, found '\n'  (at char 6), (line:1, col:7)

С добавленными условиями NL вы увидите:

['A)', 'Red', '\n', 'B)', 'Green', '\n', 'C)', 'Blue']

(Перечитывая ответ @jupiterbjy, похоже, что они также включили предложение «разобрать концы строк», так что их ответ был бы хорошим ответом, а мой может быть просто независимым подтверждением «да, то, что они сказали».)

Спасибо @PaulMcG! Я все еще обрабатываю... но я думаю, что вы говорите, что pyparsing будет игнорировать пробелы, если вы не используете AtLineStart(), и в этом случае новые строки должны обрабатываться явно. Есть ли какие-либо другие функции, которые вызывают это требование?

Michael Henry 03.07.2024 01:30

Я не буду приукрашивать, что pyparsing может понадобиться больше примеров в документах для таких чайников, как я, которым приходилось часами искать, как использовать line_end/LineEnd() с нуля. Но, черт возьми, это очень интерактивный и интересный способ создания парсера! Прочь, боль YACC и Лексера! Теперь я умру, используя этот парсер для всех нужд, которые мне когда-либо понадобятся в жизни!

jupiterbjy 03.07.2024 02:22

Да, я считаю, что это интуитивно понятный способ написания парсера.

Michael Henry 05.07.2024 01:21

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