Parsec: элегантный способ потреблять и сохранять входные данные

Я работаю в небольшой веб-хостинговой компании и решил написать скрипт для сортировки нашего довольно большого файла конфигурации зоны named/bind9. Я в некоторой степени доволен тем, как это получилось (по крайней мере, это работает), но меня немного беспокоит неизящность центральной функции синтаксического анализа. Для справки, типичное определение зоны выглядит так (на ведомом сервере. Мастер выглядит немного проще):

zone "somewebsite.com" {
    masters { ip.ad.dr.ess; };
    type slave;
    allow-query { any; };
    file "slave/db.somewebsite.com";
};

Файл заполнен примерно 190 из них. Что мне нужно от каждой зоны, так это имя сайта (для использования в качестве ключа сортировки) и вся строка, содержащая зону. Итак, вот мои парсеры (и крошечный тип данных для хранения имени зоны и ее полного текста):

type SortKey = String
type ZoneText = String
data Zone = Zone SortKey ZoneText deriving Show

allZonesParser :: Parser [Zone]
allZonesParser = do zones <- many zoneParser 
                    return zones

zoneParser :: Parser Zone
zoneParser = do p1 <- string "zone"
                p2 <- many space
                p3 <- string "\""
                zoneName <- many (alphaNum <|> oneOf ".-")
                p4 <- string "\""
                p5 <- many space
                p6 <- manyTill anyChar (try (string ";" >> newline >> string "};"))
                p7 <- many space
                p8 <- many newline
                return $ Zone zoneName (p1 ++ p2 ++ p3 ++ zoneName ++ p4 ++ p5 ++ p6 ++ ";\n};" ++ p7 ++ p8)

Я понимаю, что этот парсер не будет работать для всех вариантов использования, но для нашей конфигурации зоны он был достаточно продвинутым. Он захватывает весь раздел зоны, пока не найдет ;\n};, а затем перестраивает текст зоны. И вот моя главная жалоба: я не могу понять, как сохранить всю строку, представляющую зону, без использования 9 монадических привязок, а затем объединения их вместе с оператором ++. Есть ли элегантный способ потреблять весь этот ввод и сохранять/использовать все, что было проанализировано? Мне нужно использовать проанализированную строку позже, чтобы написать новый файл конфигурации отсортированной зоны, и кажется нелепым «реконструировать» строку так, как я сделал здесь. Я прочитал значительную часть документации Parsec, но не нашел правильного способа собрать это вместе.

Мой полный код находится здесь. Я бы посоветовал не использовать его, если вы не измените его, чтобы он соответствовал тому, как ваша конфигурация зоны разделена и разделена новой строкой.


person Mortimer McMire    schedule 30.07.2014    source источник
comment
Почему бы не преобразовать спецификацию зоны в пользовательский тип данных, а затем выделить для этого типа симпатичный принтер? Вам действительно нужно сохранять отступы и пробелы при печати?   -  person bheklilr    schedule 31.07.2014
comment
Еще одно примечание к вашему полному коду: вы определяете compareZone, но вместо этого вы можете использовать ту же реализацию для определения Ord вместо Zone, а затем вы можете просто использовать sort вместо sortBy compareZone. Просто замените подпись compareZone на instance Ord Zone where, отступ compareZone's definition over, and rename it to compare, then replace sortBy compareZone` на sort. Я бы посчитал это более идиоматичным Haskell   -  person bheklilr    schedule 31.07.2014
comment
@bheklilr Спасибо за ответ! Я думаю, что это имеет смысл для более крупного сценария общего назначения, но я пытаюсь выяснить, какое более элегантное решение было бы в этом случае использования. Если бы у меня был тип данных для полного представления определения зоны, я бы получил возможно более правильный, но, казалось бы, слишком сложный сценарий. Я неправильно истолковал ваш комментарий? Что касается вашего второго комментария: я полностью согласен, и спасибо за предложение. Это была просто слепота с моей стороны.   -  person Mortimer McMire    schedule 31.07.2014
comment
Это будет больше работы, и для вашего случая это может считаться чрезмерным. Если вы хотите избежать этого, вы можете вместо этого прибегнуть к некоторым более простым методам, которые предоставляет Haskell. Разберите первую строку, найдите последнюю и сохраните весь текст между ними. Используйте lines, чтобы получить каждую строку, затем удалите пробелы, добавьте " " к каждой, затем используйте unlines, чтобы соединить их все вместе. Тогда вам не нужны все привязки. Вы также должны посмотреть на комбинатор between, это поможет с вашей проблемой цитаты.   -  person bheklilr    schedule 31.07.2014
comment
Хотел бы я наградить вас ответом за это. Да, я все еще новичок в способах мышления Haskelly, и это был мой первый раз, когда я был достаточно смелым, чтобы использовать его в скрипте системного администратора. Итак, я понял, что вопрос, который я задаю, на самом деле не является тем, для чего был разработан Parsec. Я могу принять это. Я попробую переписать функцию синтаксического анализа, используя ваши идеи, ради собственного образования.   -  person Mortimer McMire    schedule 31.07.2014
comment
Я не говорю, что использовать Parsec для этой задачи неправильно, просто кажется, что проще вместо этого использовать некоторые встроенные функции Haskell. Вы, безусловно, можете сделать это с помощью parsec, и он, вероятно, будет работать лучше, если вы добавите функции в скрипт (то есть, если вы добавите функции), но для быстрого и грязного есть другие инструменты, которые я бы посчитал проще   -  person bheklilr    schedule 31.07.2014


Ответы (1)


Вы, конечно, можете сделать это намного короче, используя Parsec, вот что я придумал

zoneParse = do
    string "zone"
    space1 <- many space
    zoneName <- between (char '"') (char '"') (many $ noneOf "\"")
    body <- manyTill anyChar (try $ string ";\n};")
    return $ Zone zoneName $ concat ["zone", space1, "\"", zoneName, "\"", body, ";\n};"]

Здесь я уменьшил количество выполняемых привязок, потому что некоторые из них просто захватывают строковый литерал и могут быть вставлены вручную позже. Я также использовал between для захвата zoneName, так как это довольно удобный комбинатор в Parsec. После этого он просто анализирует все символы, пока не будет обнаружен ;\n}; (делает то же самое, что и string ";" >> newline >> string "};", если у вас нет \r\n в ваших файлах, в противном случае придерживайтесь версии с newline), а затем перестраивает строку, используя concat

person bheklilr    schedule 30.07.2014