Пытаясь собрать очень простой пример, чтобы проиллюстрировать проблему, с которой я столкнулся при анализе 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()
не влияют на результаты этого скрипта.)
Мой друг! Вы забыли обернуть необработанную строку в 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']
>>>
Я забыл сказать: функции textwrap.dedent()
и strip()
предназначены для отражения сценария, который я пытаюсь отладить; Мне кажется, что pyparsing неправильно обрабатывает пробелы (несмотря на то, что в документации говорится, что он игнорирует пробелы!). В этом случае необходим textwrap.dedent()
, потому что AtLineStart()
не будет соответствовать моим ключевым словам, если они имеют отступ - я подтвердил, что входные данные, которые я пытаюсь проанализировать, фактически содержат ключевые слова сразу после символа новой строки.
@MichaelHenry Интересно, есть ли в этом источнике какой-нибудь скрытый возврат каретки или какие-нибудь вкладки... интересно. Можете ли вы обновить ответ, включив в него и эти детали?
Огромное спасибо @jupiterbjy! Исключительная работа! Когда я это опубликовал, я знал, что мне будет неловко. Все сводится к отсутствию у меня навыков Python;) Я заключил тестовый текст в круглые скобки, потому что, по моему мнению, именно так вы создаете список (спасибо, lisp!). Для меня квадратные скобки предназначены для массивов (я все еще люблю тебя, C), что и было основной причиной моей проблемы.
@MichaelHenry о, ты можешь сделать то же самое в кортеже! Но поскольку в Python (и некоторых других языках) используется (), чтобы разрешить перемещение R-значения от L-значения в случае, если L-значение становится слишком длинным или т. д., это неотличимо от определения кортежа с одним значением, поэтому мы используем (val,)
, чтобы указать, что это действительно кортеж с одним значением (эта конечная запятая). Кстати, я до сих пор помню, как профессор дал мне аппаратное обеспечение Lisp и Prolog для реализации пузырьковой сортировки и n-queen, замечательные люди могут быть достаточно умными, чтобы использовать это с моей точки зрения!
Да, и поскольку в Python list
похожи на std::vector<std::variant>
не связанный список, так что... каким-то образом это может быть массив, по крайней мере, внутри? Хотя «настоящие массивы» в Python выглядят немного иначе, например import array \ array.array("typecode", Iterable)
, но в основном инициализируются списками типа array.array("i", [1, 2, 3])
— это может быть полезно для запоминания!
Спасибо за вашу настойчивость в изучении 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()
, и в этом случае новые строки должны обрабатываться явно. Есть ли какие-либо другие функции, которые вызывают это требование?
Я не буду приукрашивать, что pyparsing
может понадобиться больше примеров в документах для таких чайников, как я, которым приходилось часами искать, как использовать line_end/LineEnd()
с нуля. Но, черт возьми, это очень интерактивный и интересный способ создания парсера! Прочь, боль YACC и Лексера! Теперь я умру, используя этот парсер для всех нужд, которые мне когда-либо понадобятся в жизни!
Да, я считаю, что это интуитивно понятный способ написания парсера.
Спасибо за ваш ответ @jupiterbjy! Я действительно хочу
+
, поскольку текст, который я анализирую, имеет шесть токенов, а моя грамматика состоит из шести элементов, каждый из которых должен соответствовать одному токену. Использование|
, как вы это сделали, будет соответствоватьA) Red\nA) Green\nA) Blue
, но это недопустимый вход для моего парсера — ключевые словаA)
,B)
иC)
значимы и должны появляться по порядку.