Использование pyparsing и рекурсии - PullRequest
1 голос
/ 04 мая 2020

Я пытаюсь создать синтаксический анализатор для строки, содержащей один или несколько из следующих шаблонов:

  1. -flag
  2. -flag object
  3. -flag object {nested}
  4. -flag {object {nested}}

Например, я буду использовать следующую строку:

-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }

Для ее анализа Я использую:

exp = '-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }'
only_flag = '-' + Word(printables, excludeChars='-').setResultsName("flag")
flag_w_obj = only_flag + Optional("{") + Word(printables, excludeChars='-').setResultsName("object")
flag_w_obj_w_nested = flag_w_obj + originalTextFor(nestedExpr("{", "}")).setResultsName("nested_expr")

parser = flag_w_obj_w_nested | flag_w_obj | only_flag
parsed = parser.searchString(exp)

Как я могу оценить вложенное выражение по тем же правилам, чтобы получить вложенные флаги и объекты?

Мой окончательный желаемый результат - создать диктат, содержащий данные в формат:

{
  "flag1": null,
  "flag2": {
    "object1": {
      "nested_flag1": "nested_obj1",
      "nested_flag2": null
    }
  }
}

1 Ответ

0 голосов
/ 11 мая 2020

В большинстве случаев, когда у вас есть выражение с вложенным содержимым, вы в конечном итоге будете использовать выражение Forward с пиапарсингом. Forward позволяет ссылаться на выражение, которое еще не полностью определено - это предварительное объявление. В вашем парсере форма -flag args... сама может содержать флаг, вложенный в {}. Как только содержимое может быть указано, тогда «назначение» может быть выполнено с использованием оператора <<=.

Я также настоятельно рекомендую написать краткий дизайн синтаксического анализатора, прежде чем вы начнете фактически писать какой-либо код (не делайте этого независимо от того, какую библиотеку вы планируете использовать). При анализе BNF (форма Бэкуса-Наура) является типичным форматом для использования. Он не должен быть очень строгим, но достаточно подробным, чтобы вы могли вручную обработать его, чтобы убедиться, что все правильно, и нет двусмысленностей. Также полезно написать несколько примеров строк, которые, как вы ожидаете, сможет обработать синтаксический анализатор.

Следующий аннотированный код показывает BNF, который я написал для вашей проблемы, и реализацию его в синтаксическом анализаторе.

"""
BNF

flag_name := '-' alphanumeric...
flag_arg_word := alphanumeric...

flag_arg := flag_arg_word | '{' flag_expr... '}'
flag_expr := flag_name [flag_arg...]
"""

import pyparsing as pp

# define punctuation
LBRACE, RBRACE = map(pp.Suppress, "{}")

# recursive parser requires a forward declaraction
flag_expr = pp.Forward()

# implement BNF definitions
flag_name = pp.Word("-", pp.alphanums + "_")
flag_arg_word = pp.Word(pp.alphas, pp.alphanums + "_")
flag_arg = flag_arg_word | (LBRACE + flag_expr[...] + RBRACE)

# use '<<=' operator to define recursive expression
flag_expr <<= pp.Group(flag_name + pp.Group(flag_arg[...]))

# a command is 0 or more flag_exprs
cmd_expr = flag_expr[...]

# test it out
cmd = "-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }"
parsed = cmd_expr.parseString(cmd)

# convert to a list and show with pprint
from pprint import pprint
pprint(parsed.asList(), width=12)

# runTests is very useful for running several test strings through a parser
cmd_expr.runTests("""\
-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }
""")
...