Использование pyparsing для разбора экранирования слова -разбиение на несколько строк

StackOverflow https://stackoverflow.com/questions/1735722

  •  20-09-2019
  •  | 
  •  

Вопрос

Я пытаюсь разобрать слова, которые могут быть разбиты на несколько строк комбинацией обратной косой черты и новой строки ("\\n") используя пипарсинг.Вот что я сделал:

from pyparsing import *

continued_ending = Literal('\\') + lineEnd
word = Word(alphas)
split_word = word + Suppress(continued_ending)
multi_line_word = Forward()
multi_line_word << (word | (split_word + multi_line_word))

print multi_line_word.parseString(
'''super\\
cali\\
fragi\\
listic''')

Результат, который я получаю, таков ['super'], в то время как ожидаемый результат равен ['super', 'cali', fragi', 'listic'].А еще лучше было бы объединить их все в одно слово (что, я думаю, я могу просто сделать с multi_line_word.parseAction(lambda t: ''.join(t)).

Я попытался просмотреть этот код в помощник по pyparsing, но это выдает мне ошибку, maximum recursion depth exceeded.

РЕДАКТИРОВАТЬ 2009-11-15: Позже я понял, что pyparsing становится немного щедрым в отношении пробелов, и это приводит к некоторым ошибочным предположениям о том, что то, для чего, как я думал, я анализировал, было намного слабее.То есть мы не хотим видеть пробелов ни между одной из частей слова, escape-символом и символом EOL.

Я понял, что приведенной выше небольшой строки примера недостаточно в качестве тестового примера, поэтому я написал следующие модульные тесты.Код, который проходит эти тесты, должен соответствовать тому, что я интуитивно воспринимаю как слово с разделением на экранирование — и Только разделенное на побег слово.Они не будут соответствовать базовому слову, которое не является escape-split.Мы можем — и я считаю, должны — использовать для этого другую грамматическую конструкцию.Это позволяет поддерживать порядок, поскольку они разделены на две части.

import unittest
import pyparsing

# Assumes you named your module 'multiline.py'
import multiline

class MultiLineTests(unittest.TestCase):

    def test_continued_ending(self):

        case = '\\\n'
        expected = ['\\', '\n']
        result = multiline.continued_ending.parseString(case).asList()
        self.assertEqual(result, expected)


    def test_continued_ending_space_between_parse_error(self):

        case = '\\ \n'
        self.assertRaises(
            pyparsing.ParseException,
            multiline.continued_ending.parseString,
            case
        )


    def test_split_word(self):

        cases = ('shiny\\', 'shiny\\\n', ' shiny\\')
        expected = ['shiny']
        for case in cases:
            result = multiline.split_word.parseString(case).asList()
            self.assertEqual(result, expected)


    def test_split_word_no_escape_parse_error(self):

        case = 'shiny'
        self.assertRaises(
            pyparsing.ParseException,
            multiline.split_word.parseString,
            case
        )


    def test_split_word_space_parse_error(self):

        cases = ('shiny \\', 'shiny\r\\', 'shiny\t\\', 'shiny\\ ')
        for case in cases:
            self.assertRaises(
                pyparsing.ParseException,
                multiline.split_word.parseString,
                case
            )


    def test_multi_line_word(self):

        cases = (
                'shiny\\',
                'shi\\\nny',
                'sh\\\ni\\\nny\\\n',
                ' shi\\\nny\\',
                'shi\\\nny '
                'shi\\\nny captain'
        )
        expected = ['shiny']
        for case in cases:
            result = multiline.multi_line_word.parseString(case).asList()
            self.assertEqual(result, expected)


    def test_multi_line_word_spaces_parse_error(self):

        cases = (
                'shi \\\nny',
                'shi\\ \nny',
                'sh\\\n iny',
                'shi\\\n\tny',
        )
        for case in cases:
            self.assertRaises(
                pyparsing.ParseException,
                multiline.multi_line_word.parseString,
                case
            )


if __name__ == '__main__':
    unittest.main()
Это было полезно?

Решение 2

Поковырявшись еще немного, я наткнулся на этот поток справки где был этот примечательный момент

Я часто вижу неэффективные грамматики, когда кто-то реализует грамматику pyparsing непосредственно из определения BNF.BNF не имеет понятия "один или более", или "ноль или более", или "необязательно"...

После этого мне пришла в голову идея изменить эти две строки

multi_line_word = Forward()
multi_line_word << (word | (split_word + multi_line_word))

Для

multi_line_word = ZeroOrMore(split_word) + word

Это позволило вывести то, что я искал: ['super', 'cali', fragi', 'listic'].

Затем я добавил действие синтаксического анализа, которое объединило бы эти токены вместе:

multi_line_word.setParseAction(lambda t: ''.join(t))

Это дает окончательный результат ['supercalifragilistic'].

Главный посыл, который я усвоил, заключается в том, что человек не просто отправиться в Мордор.

Просто шучу.

Главный вывод заключается в том, что нельзя просто реализовать однозначный перевод BNF с помощью pyparsing.Следует использовать некоторые хитрости с использованием итеративных типов.

РЕДАКТИРОВАТЬ 2009-11-25: Чтобы компенсировать более сложные тестовые примеры, я изменил код следующим образом:

no_space = NotAny(White(' \t\r'))
# make sure that the EOL immediately follows the escape backslash
continued_ending = Literal('\\') + no_space + lineEnd
word = Word(alphas)
# make sure that the escape backslash immediately follows the word
split_word = word + NotAny(White()) + Suppress(continued_ending)
multi_line_word = OneOrMore(split_word + NotAny(White())) + Optional(word)
multi_line_word.setParseAction(lambda t: ''.join(t))

Преимущество этого заключается в том, что между элементами нет пробелов (за исключением перевода строк после экранирующей обратной косой черты).

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

Вы довольно близки к своему коду.Любой из этих модов будет работать:

# '|' means MatchFirst, so you had a left-recursive expression
# reversing the order of the alternatives makes this work
multi_line_word << ((split_word + multi_line_word) | word)

# '^' means Or/MatchLongest, but beware using this inside a Forward
multi_line_word << (word ^ (split_word + multi_line_word))

# an unusual use of delimitedList, but it works
multi_line_word = delimitedList(word, continued_ending)

# in place of your parse action, you can wrap in a Combine
multi_line_word = Combine(delimitedList(word, continued_ending))

Как вы обнаружили в своем поиске в Google, переводы BNF->pyparsing должны выполняться с особым учетом использования функций pyparsing вместо недостатков BNF.На самом деле я был в процессе написания более длинного ответа, вдаваясь в подробности перевода BNF, но вы уже нашли этот материал (я полагаю, в вики).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top