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 魔法,这将导致类型更加复杂,我认为,在这里不值得。
我写了这样的排列解析例子:
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 魔法,这将导致类型更加复杂,我认为,在这里不值得。