трюк с вложенными структурами в pyparsing

Я изо всех сил пытаюсь разобрать вложенные структуры с помощью PyParsing. Я просмотрел множество "вложенных" примеров использования PyParsing, но не не вижу, как решить мою проблему.

Вот как выглядит моя внутренняя структура:

texture_unit optionalName
{
    texture required_val
    prop_name1 prop_val1
    prop_name2 prop_val1
}

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

pass optionalName
{
    prop_name1 prop_val1
    prop_name2 prop_val1

    texture_unit optionalName
    {
        // edit 2: showing use of '.' character in value
        texture required_val.file.name optional_val // edit 1: forgot this line in initial post.

        // edit 2: showing potentially multiple values
        prop_name3 prop_val1 prop_val2
        prop_name4 prop_val1
    }
}

Я успешно разбираю внутреннюю структуру. Вот мой код для этого.

prop_ = pp.Group(pp.Word(pp.alphanums+'_')+pp.Group(pp.OneOrMore(pp.Word(pp.alphanums+'_'+'.'))))
texture_props_ = pp.Group(pp.Literal('texture') + pp.Word(pp.alphanums+'_'+'.')) + pp.ZeroOrMore(prop_)
texture_ = pp.Forward()
texture_ << pp.Literal('texture_unit').suppress() + pp.Optional(pp.Word(pp.alphanums+'_')).suppress() + pp.Literal('{').suppress() + texture_props_ + pp.Literal('}').suppress()

Вот моя попытка разобрать внешнюю структуру,

pass_props_ = pp.ZeroOrMore(prop_)
pass_ = pp.Forward()
pass_ << pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() + pp.Literal('{').suppress() + pass_props_ + pp.ZeroOrMore(texture_) + pp.Literal('}').suppress()

Когда я говорю: pass_.parseString( testPassStr )

Я вижу ошибки в консоли, которые ожидались "}".

Я вижу, что это очень похоже на пример структуры C, но я не уверен, что отсутствует магия. Мне также любопытно, как управлять результирующей структурой данных при использовании nestedExpr.


person cyrf    schedule 24.05.2014    source источник
comment
Вот еще один пример поддержки вложенных структур. Похоже, он использует «pyparsing.Dict». Все эти примеры показывают разные способы реализации вложенного синтаксического анализа, что общего? pyparsing.wikispaces.com/share/view/40834661   -  person cyrf    schedule 28.05.2014


Ответы (2)


Есть две проблемы:

  1. В вашей грамматике вы пометили texture литерал как требуемый в блоке texture_unit, но во втором примере нет texture.
  2. Во втором примере pass_props_ совпадает с texture_unit optionalName. После него pp.Literal('}') ожидает }, а выдает {. Это причина ошибки.

Мы можем проверить это, изменив правило pass_ следующим образом:

pass_ << pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() + \
             pp.Literal('{').suppress() + pass_props_

print pass_.parseString(s2)

Это дает нам следующий вывод:

[['prop_name', ['prop_val', 'prop_name', 'prop_val', 'texture_unit', 'optionalName']]]

Мы видим, что pass_props_ совпадает с texture_unit optionalName.
Итак, что мы хотим сделать: prop_ может содержать alphanums, _ и ., но не может совпадать с литералом texture_unit. Мы можем сделать это с помощью regex и отрицательного прогноза:

prop_ = pp.Group(  pp.Regex(r'(?!texture_unit)[a-z0-9_]+')+ pp.Group(pp.OneOrMore(pp.Regex(r'(?!texture_unit)[a-z0-9_.]+'))) )

Наконец, рабочий пример будет выглядеть так:

import pyparsing as pp

s1 = '''texture_unit optionalName
    {
    texture required_val
    prop_name prop_val
    prop_name prop_val
}'''

prop_ = pp.Group(  pp.Regex(r'(?!texture_unit)[a-z0-9_]+')+ pp.Group(pp.OneOrMore(pp.Regex(r'(?!texture_unit)[a-z0-9_.]+'))) )
texture_props_ = pp.Group(pp.Literal('texture') + pp.Word(pp.alphanums+'_'+'.')) + pp.ZeroOrMore(prop_)
texture_ = pp.Forward()
texture_ = pp.Literal('texture_unit').suppress() + pp.Word(pp.alphanums+'_').suppress() +\
           pp.Literal('{').suppress() + pp.Optional(texture_props_) + pp.Literal('}').suppress()

print texture_.parseString(s1)

s2 = '''pass optionalName
{
    prop_name1 prop_val1.name
    texture_unit optionalName1
    {
        texture required_val1
        prop_name2 prop_val12
        prop_name3 prop_val13
    }
    texture_unit optionalName2
    {
        texture required_va2l
        prop_name2 prop_val22
        prop_name3 prop_val23
    }
}'''

pass_props_ = pp.ZeroOrMore(prop_  )
pass_ = pp.Forward()

pass_ = pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() +\
        pp.Literal('{').suppress() + pass_props_ + pp.ZeroOrMore(texture_ ) + pp.Literal('}').suppress()

print pass_.parseString(s2)

Выход:

[['texture', 'required_val'], ['prop_name', ['prop_val', 'prop_name', 'prop_val']]]
[['prop_name1', ['prop_val1.name']], ['texture', 'required_val1'], ['prop_name2', ['prop_val12', 'prop_name3', 'prop_val13']], ['texture', 'required_va2l'], ['prop_name2', ['prop_val22', 'prop_name3', 'prop_val23']]]
person NorthCat    schedule 25.05.2014
comment
1. Вы правы, в моем вложенном примере отсутствовало необходимое свойство «текстура». Это была опечатка при публикации. Поправлю в посте. - person cyrf; 27.05.2014
comment
@cyrf А как насчет второго пункта и решения для него? - person NorthCat; 27.05.2014
comment
о № 2, спасибо за отличное предложение. Я все еще тестирую его. Я пытаюсь понять, почему «негативный просмотр вперед» не понадобился в примере C Struct Parser, который поддерживает вложенные структуры C (ссылка в моем исходном посте). - person cyrf; 27.05.2014
comment
Новое определение 'prop_' прерывает синтаксический анализ внутренней структуры. Я должен был сделать тест более явным, я пытался сделать его общим и читабельным. Сейчас я отредактирую свой пост, чтобы лучше указать, что мне нужно. Можете ли вы прокомментировать, почему в примере со структурой C не нужен «отрицательный просмотр вперед»? - person cyrf; 28.05.2014
comment
Если во внутренней структуре не используется '.' в prop_val, затем анализируется внутренняя структура. Однако синтаксический анализ внешней структуры с вашими изменениями по-прежнему приводит к ошибкам. - person cyrf; 28.05.2014
comment
мои извинения, ваш код работает. Мне пришлось изменить вторые назначения texture_ и pass_, чтобы использовать оператор ‹‹ (вместо оператора =), но он работает. Я не уверен, почему это не работает, когда я вставляю его в свой код. Проверка... - person cyrf; 28.05.2014

Ответ, который я искал, связан с использованием парсера Forward, показанного в примере Cstruct (связанном в OP).

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

«Хитрость» в определении грамматики pyparsing для вложенной структуры заключается в том, чтобы отложить определение структуры, но включить «предварительно объявленную» версию структуры при определении элементов структуры, чтобы члены также могли включать структуру. . Затем завершите грамматику структуры в виде списка членов.

struct = Forward()
member = blah | blah2 | struct
struct << ZeroOrMore( Group(member) )

Это также обсуждается здесь: Pyparsing: анализ Вложенные данные открытого текста JSON в список

OP (мой) описывал тестовые данные и грамматику, которые не были достаточно конкретными и совпадали, когда они должны были дать сбой. @NorthCat правильно обнаружил нежелательные совпадения в грамматике. Однако предложение определить множество «отрицательных прогнозов вперед» казалось неуправляемым.

Вместо того, чтобы определять, что не должно совпадать, мое решение вместо этого явно перечисляло возможные совпадения. Совпадения были ключевыми словами-членами, используя 'oneOf('список слов, разделенных пробелом'). Как только я указал все возможные совпадения, я понял, что моя структура была не вложенной структурой, а структурой с конечной глубиной и разными грамматиками, описывающими каждую глубину. Таким образом, мое определение члена не требовало трюка с предварительным объявлением.

Терминатор моих определений элементов был другим, чем в примере с Cstruct. Вместо завершения с помощью ';' (точка с запятой), как и в C++, мои определения членов должны заканчиваться в конце строки. В pyparsing вы можете указать конец строки с помощью парсера LineEnd. Итак, я определил свои элементы как список значений, НЕ включая «LineEnd», например, обратите внимание на использование оператора «Not» (~) в последнем определении:

EOL = LineEnd().suppress()
ident = Word( alphas+"_", alphanums+"_$@#." )
integer = Word(nums)
real = Combine(Optional(oneOf('+ -')) + Word(nums) + '.' + Optional(Word(nums)))
propVal = real | integer | ident
propList = Group(OneOrMore(~EOL + propVal))
person cyrf    schedule 07.08.2014