Разбор пользовательского формата конфигурации в 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 дня, чтобы разобраться.