Как я могу развернуть результаты из вспомогательных функций pyparsing?

#python #pyparsing

#питон #pyparsing

Вопрос:

В настоящее время я нахожусь в процессе реализации диалекта prolog в python. Я использую замечательный pyparsing модуль для этой цели, и я обнаружил, что он очень хорошо работает для других проектов, связанных с контекстно-свободными грамматиками.

По мере того, как я перехожу к контекстно-зависимым грамматикам, я постепенно привыкаю к pyparsing стилю. pyparsing.nestedExpr и pyparsing.delimitedList это две вещи, с которыми я все еще знакомлюсь. Прямо сейчас у меня возникли проблемы с pyparsing.delimitedList ; он достигает того, что я ищу, но каждый человек term в приведенном ниже примере кода возвращается в списке, и я не использовал pyparsing.Group ни на каких условиях.

Рефакторинг для использования pyparsing.nestedExpr и pyparsing.infixNotation являются следующими в моих задачах после решения этой проблемы, поэтому, пожалуйста, не паникуйте, что я их еще не использую. Я также подозреваю, но пока не знаю, что мне придется предотвращать совпадения для term_list в левой части выражения правила. Это означает, что код находится в стадии разработки и со временем будет претерпевать значительные изменения по мере дальнейших экспериментов с библиотекой.

Я думаю pyparsing.ungroup , что может быть использован для решения проблемы, но pyparsing.ungroup(pyparsing.delimitedList... , похоже, в данном случае это не имеет никакого эффекта.

Логика вывода

 result = root.parseString('''
A :- True
Z :- 5
''')
print(result.dump())
print(result.rules[0].goals)
 

Результаты

 [['A', 'True'], ['Z', '5']]
- rules: [['A', 'True'], ['Z', '5']]
  [0]:
    ['A', 'True']
    - goals: [['True']]
      [0]:
        ['True']
  [1]:
    ['Z', '5']
    - goals: [['5']]
      [0]:
        ['5']
[['True']]
 

Ожидаемые результаты

 [['A', 'True'], ['Z', '5']]
- rules: [['A', 'True'], ['Z', '5']]
  [0]:
    ['A', 'True']
    - goals: ['True']
  [1]:
    ['Z', '5']
    - goals: ['5']
['True']
 

Полный код

 import pyparsing as pp

# These types are the language primitives
atom = pp.Word(pp.alphanums)
number = pp.Word(pp.nums)
variable = pp.Word(pp.alphanums)
string = pp.quotedString

# Terms are the basic unit of expression here
compound_term = pp.Forward()
term = (atom ^ number ^ variable ^ pp.Group(compound_term))('terms*')

# A compound term includes a few rules for term composition, such as lists or an atom containing arguments
term_list = pp.Forward()
compound_term <<= 
string ^ 
term_list ^ 
atom('functor')   pp.Suppress('(')   pp.delimitedList(term('arguments*'))   pp.Suppress(')')

term_list <<= pp.Suppress('[')   pp.delimitedList(term('items*'))   pp.Suppress(']')

# The rule operator is an infix operator represented by :-
# On the right side, multiple goals can be composed using AND or OR operators
rule = pp.Group(
    term   pp.Suppress(':-')   
    pp.delimitedList(term('goals*')) 
    )('rules*')

root = pp.ZeroOrMore(rule)

result = root.parseString(
    '''
    A :- True
    Z :- 5
    ''')
print(result.dump())
print(result.rules[0].goals)
 

Ответ №1:

Первоначальная проблема заключается в наличии Group в compound_term :

 term = (atom ^ number ^ variable ^ pp.Group(compound_term))('terms*')
 

должно быть

 term = (atom ^ number ^ variable ^ (compound_term))('terms*')
 

После внесения этого изменения и добавления названия результатов «lhs» в ваше правило (см. Ниже), я получаю следующее:

 [['A', 'True'], ['Z', '5']]
- rules: [['A', 'True'], ['Z', '5']]
  [0]:
    ['A', 'True']
    - goals: ['True']
    - lhs: 'A'
  [1]:
    ['Z', '5']
    - goals: ['5']
    - lhs: 'Z'
['True']
 

Некоторые добавленные примечания:

  1. atom определяется как
     atom = pp.Word(pp.alphanums)
     

    Это будет соответствовать «123» atom также. Чтобы убедиться , что вы просто получаете имена переменных , используйте pp.Word(pp.alphas, pp.alphanums) . Это указывает на то, что начальная буква должна быть буквой, а любые последующие буквы могут быть буквенными или цифровыми (то же самое для variable ).

  2. Я бы не стал добавлять название результатов «термины *» в термин, так как в конечном итоге оно будет использоваться как с левой, так и с правой стороны вашего оператора «:-«. Я рекомендую, чтобы люди обычно оставляли вложение имен результатов до тех пор, пока это выражение не будет использоваться в выражениях более высокого уровня. Например, я бы определил правило как:
     rule = pp.Group(term("rule_lhs") 
                      ":-" 
                      pp.delimitedList(term)("goals") 
                    )
     
  3. Я бы на самом деле не назвал «:-» «инфиксным» оператором, я рассматриваю такие операторы, как » «, «-«, «И», «ИЛИ», как инфиксные операторы. Например, я не думаю x :- y :- z , что это действительно так. Вероятно, вы сделаете что-то подобное, чтобы добавить свои операторы «И» и «ИЛИ»:
     logical_term_expression = pp.infixNotation(term,
                [
                ("amp;amp;", 2, pp.opAssoc.LEFT,),
                ("||", 2, pp.opAssoc.LEFT,),
                ])
     

    Наличие имени результата term действительно приведет к путанице, с большей вероятностью будут использоваться классы в ваших кортежах операторов, как вы можете видеть в примерах pyparsing, таких как simple_bool.py .

  4. Вы упомянули об использовании nestedExpr — пожалуйста, не делайте этого. Этот помощник лучше всего использовать при написании сканера для чего-то вроде кода на C, где вы можете просто перепрыгнуть через некоторые вложенные фигурные скобки, фактически не анализируя содержимое. В вашем DSL вы захотите разобрать все правильно — но я думаю infixNotation , это может быть все, что вам нужно.

Комментарии:

1. Спасибо за подробный ответ! Я отметил это как правильный ответ из-за множества рекомендаций по наилучшей практике, которые вы включили, а также из-за наличия решения, которое я реализовал самостоятельно. Хотя мне несколько неудобно использовать «lhs» вместо чего-то более специфичного для выражения, это в основном вопрос стиля. Приношу свои извинения за задержку!