Разбор пользовательского формата конфигурации в Python

Я пишу менеджер профилей для игры Stellaris, и я столкнулся с их форматом, в котором они хранят информацию о модах и настройках.

Файл мода:

name="! (Ship Designer UI Fix) !"
path="mod/ship_designer_ui_fix"
tags={
    "Fixes"
}
remote_file_id="879973318"
supported_version="1.6"

Настройки:

language="l_english"
graphics={
    size={
        x=1920
        y=1200
    }
    min_gui={
        x=1920
        y=1200
    }
    gui_scale=1.000000
    gui_safe_ratio=1.000000
    refreshRate=59
    fullScreen=no
    borderless=no
    display_index=0
    shadowSize=2048
    multi_sampling=8
    maxanisotropy=16
    gamma=50.000000
    vsync=yes
}
last_mods={
    "mod/ship_designer_ui_fix.mod"
    "mod/ugc_720237457.mod"
    "mod/ugc_775944333.mod"
}

Я думал pyparsing будет полезен там (и это, вероятно, будет), но прошло много времени с тех пор, как я на самом деле делал что-то подобное, и вот это я невежественный.

Я должен извлечь простое key=value но я изо всех сил пытаюсь действительно оттуда уйти, чтобы иметь возможность извлекать массивы, не говоря уже о многоуровневых массивах.

lbrack = Literal("{").suppress()
rbrack = Literal("}").suppress()
equals = Literal("=").suppress()

nonequals = "".join([c for c in printables if c != "="]) + " \t"

keydef = ~lbrack + Word(nonequals) + equals + restOfLine

conf = Dict( ZeroOrMore( Group(keydef) ) )
tokens = conf.parseString(data)

Я не очень далеко, как вы можете видеть. Кто-нибудь может указать мне на следующий шаг? Я не спрашиваю готового и рабочего решения для всего этого - это бы сильно меня продвинуло вперед, но где в этом удовольствие:)

1 ответ

Решение

Ну, это очень заманчиво - просто погрузиться и написать этот парсер, но вы хотите, чтобы это было забавно для себя, это здорово.

Прежде чем писать какой-либо код, напишите BNF. Таким образом, вы напишите достойный и надежный парсер, а не просто "все, что не является знаком равенства, должно быть идентификатором".

Здесь много битов "что-то = что-то", посмотрите на вещи справа и слева от "=". Все левые части выглядят как довольно хорошо продуманные идентификаторы: альфа, подчеркивание. Я мог бы также представить числовые цифры, если они не являются ведущим символом. Итак, скажем, левые части будут идентификаторами:

identifier_leading = 'A'..'Z' 'a'..'z' '_'
identifier_body = identifier_leading '0'..'9'
identifier ::= identifier_leading + identifier_body*

Правые стороны представляют собой смесь вещей:

  • целые
  • поплавки
  • "да" или "нет" логическое значение
  • строки в кавычках
  • что-то в фигурных скобках

"Что-то в скобках" - это либо список строк в кавычках, либо список пар "идентификатор = значение". Я пропущу ужасные детали определения чисел с плавающей точкой и целых чисел и строк в кавычках, давайте просто предположим, что они определены:

boolean_value ::= 'yes' | 'no'
value ::= float | integer | boolean_value | quoted_string | string_list_in_braces | key_value_list_in_braces
string_list_in_braces ::= '{' quoted_string * '}'
key_value ::= identifier '=' value
key_value_list_in_braces ::= '{' key_value* '}'

Вам придется использовать pyparsing Forward объявить value прежде чем он будет полностью определен, так как он используется в key_value, но key_value используется в key_value_list_in_braces, который используется для определения value - рекурсивная грамматика. Вы уже знакомы с Dict(OneOrMore(Group(named_item))) шаблон, и это должно быть хорошо, чтобы дать вам структуру полей, которые доступны по имени. За identifier, Word будет работать, или вы можете просто использовать предопределенный pyparsing_common.identifier который был введен как часть pyparsing_common пространство имён в прошлом году.

Перевод с BNF на pyparsing должен быть в значительной степени 1 к 1 отсюда. В этом отношении из BNF вы также можете использовать PLY, ANTLR или другую библиотеку синтаксического анализа. BNF действительно стоит потратить 1/2 часа или 1/2 дня, чтобы разобраться.

Другие вопросы по тегам