Parsec:消费和保存输入的优雅方式

时间:2014-07-30 20:12:15

标签: haskell 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个monadic绑定,然后将它们与++运算符拼接在一起。是否有一种优雅的方式来消耗所有这些输入并保留/使用已解析的所有内容?我需要稍后使用解析后的字符串来编写一个新的排序区域配置文件,并且重建&#34;这似乎很荒谬。字符串我在这里做的方式。我已经阅读了Parsec documentation的很大一部分而没有找到合适的方法将它拼凑在一起。

我的完整代码是here。我建议不要使用它,除非你修改它以适应你的区域配置间隔和换行的方式。

1 个答案:

答案 0 :(得分: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 {1}}),然后使用concat

重建字符串