Parsec 排列解析

Parsec permutation parsing

我写了这样的排列解析例子:

data Entry = Entry {
    first_name :: String
  , last_name :: String
  , date_of_birth :: Maybe String
  , nationality :: Maybe String
  , parentage :: Maybe String
} deriving (Show)

nameParser :: Parser (String, String)
nameParser = do
  first_name <- many1 upper
  endOfLine
  last_name <- many1 letter
  endOfLine
  return $ (first_name, last_name)

attributeParser :: String -> Parser String
attributeParser field = do
  string $ field ++ ": "
  value <- many1 (noneOf "\n")
  endOfLine
  return value

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> (Nothing, liftM Just (try $ attributeParser "Date of Birth"))
    <|?> (Nothing, liftM Just (try $ attributeParser "Nationality"))
    <|?> (Nothing, liftM Just (try $ attributeParser "Parentage"))
    )
  return $ Entry f l d n p

main = do
    mapM_ putStrLn . map (show . parse entryParser "") $ goodTests

goodTests =
  "AAKVAAG\nTorvild\nDate of Birth: 1 July\nNationality: Norwegian\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nNationality: Norwegian\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nParentage: business executive\nNationality: Norwegian\n" :
  "AAKVAAG\nTorvild\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nNationality: Norwegian\n" : []

将来最好用新字段扩展 Entry 数据,但这样做需要在 entryParser 函数中放入更多重复代码。有没有办法让这个函数接受解析器列表?

我从这个开始:

attributeParsers =
  map attributeParser ["Date of Birth", "Nationality", "Parentage"]

permuteParams =
  map (\p -> (Nothing, liftM Just (try p))) attributeParsers

但是无法以正确的方式将 permuteParams<|?> 运算符折叠在一起(我想这需要比 (,,) 元组构造函数更聪明的东西)。

(<|?>) 不能很好地处理折叠,因为作为第一个参数传递的 StreamPermParser 的类型与 StreamPermParser 结果的类型不同。对于一个更简单但类似的问题,如果您尝试以应用方式将 (,,)(<$>)(<*>) 一起使用(例如 (,,) <$> foo <*> bar <*> baz,您将 运行 陷入类似的问题).

如果您想减少一些重复,我平淡无奇的建议是使用本地定义:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> optField "Date of Birth"
    <|?> optField "Nationality"
    <|?> optField "Parentage"
    )
  return $ Entry f l d n p
  where
  optField fieldName = (Nothing, liftM Just (try $ attributeParser fieldName))

作为第一步,您可以抽象出您为每个组件所做的事情:

attr txt = (Nothing, liftM Just (try $ attributeParser txt))

有了这个,你可以去:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> attr "Date of Birth"
    <|?> attr "Nationality"
    <|?> attr "Parentage"
    )
  return $ Entry f l d n p

然后,如果需要,您可以组合中缀组合器和 attr 调用:

f .$ x = f <$?> attr x
f .| x = f <|?> attr x

infixl 2 .$
infixl 2 .|

这给你:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    .$ "Date of Birth"
    .| "Nationality"
    .| "Parentage"
    )
  return $ Entry f l d n p

然后你可以通过去掉中间的三元组来进一步简化。您所做的只是构建它,然后将其组件应用于 Entry f l,因此您也可以将置换解析器的结果直接应用于 Entry f l

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  permute (Entry f l
    .$ "Date of Birth"
    .| "Nationality"
    .| "Parentage"
    )

我觉得这样够紧凑了。如果你真的想要某种折叠,你要么必须引入一个中间列表并将排列结果收集到一个列表中。然而,这仅在所有可置换属性都属于同一类型(它们目前是)的情况下才有效,并且不太好,因为您将对该列表中的元素数量做出假设。或者你将不得不使用异构列表/某种类型 class 魔法,这将导致类型更加复杂,我认为,在这里不值得。