Установка максимального числа вхождений с помощью delimitedList с использованием pyparsing - PullRequest
2 голосов
/ 22 марта 2020

pyparsing предоставляет вспомогательную функцию delimitedList , которая соответствует последовательности одного или нескольких выражений , разделенных delimiter :

delimitedList(expr, delim=',', combine=False)

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

Для Например, для соответствия "foo", "bar, "baz" я использовал восходящий подход, создав токен для каждого слова:

import pyparsing as pp

dbl_quote = pp.Suppress('"')

foo = dbl_quote + pp.Literal('foo') + dbl_quote
bar = dbl_quote + pp.Literal('bar') + dbl_quote
baz = dbl_quote + pp.Literal('baz') + dbl_quote

Я хочу создать выражение, которое соответствует:

ноль или один вхождений "foo", ноль или один вхождений "bar", ноль или один вхождений "baz"

... в любой заказ . Примеры правильного ввода:

  • "foo", "bar", "baz"
  • "baz", "bar", "foo", // Порядок не важен
  • "bar", "baz" // Допускается ноль вхождений
  • "baz"
  • // Ноль вхождений всех токенов

Примеры неверного ввода:

  • "notfoo", "notbar", "notbaz"
  • "foo", "foo", "bar", "baz" // Два вхождения foo
  • "foo" "bar", "baz" // Отсутствует запятая
  • "foo" "bar", "baz", // Конечная запятая

Я тяготею к delimitedList , потому что мой ввод - это список через запятую, но теперь я чувствую, что эта функция работает против меня, а не для меня.

import pyparsing as pp

dbl_quote = pp.Suppress('"')

foo = dbl_quote + pp.Literal('foo') + dbl_quote
bar = dbl_quote + pp.Literal('bar') + dbl_quote
baz = dbl_quote + pp.Literal('baz') + dbl_quote



# This is NOT what I want because it allows tokens
# to occur more than once.
foobarbaz = pp.delimitedList(foo | bar | baz)



if __name__ == "__main__":
    TEST = '"foo", "bar", "baz"'
    results = foobarbaz.parseString(TEST)
    results.pprint()

1 Ответ

0 голосов
/ 22 марта 2020

Обычно, когда я вижу «в любом порядке» как часть грамматики, моей первой мыслью является использование Each, которое вы можете создать с помощью оператора &:

undelimited_foo_bar_baz = foo & bar & baz

Это Парсер будет анализировать foo, bar и baz в любом порядке. Если вы хотите, чтобы они были необязательными, просто заключите их в необязательные значения:

undelimited_foo_bar_baz = Optional(foo) & Optional(bar) & Optional(baz)

Но запятые в вашем входе делают этот вид грязным, поэтому в качестве запасного варианта вы можете придерживаться delimitedList (который уберет запятые) добавьте действие разбора условия для запуска после анализа списка, чтобы убедиться, что присутствовал только один из каждого соответствующего элемента:

from collections import Counter
def no_more_than_one_of_any(t):
    return all(freq == 1 for freq in Counter(t.asList()).values())
foobarbaz.addCondition(no_more_than_one_of_any, message="duplicate item found in list")

if __name__ == "__main__":
    tests = '''\
    "foo"
    "bar"
    "baz"
    "foo", "baz"
    "foo", "bar", "baz"
    "foo", "bar", "baz", "foo"
    '''
    foobarbaz.runTests(tests)

Печать:

"foo"
['foo']

"bar"
['bar']

"baz"
['baz']

"foo", "baz"
['foo', 'baz']

"foo", "bar", "baz"
['foo', 'bar', 'baz']

"foo", "bar", "baz", "foo"
^
FAIL: duplicate item found in list, found '"'  (at char 0), (line:1, col:1)
...