我有一个小的parsec解析器,可以将制表符分隔值(TSV)解析为字符串。我想切换到检查源文件中的数字和布尔值(列为" Y"或" N")。
这里是旧的TSV版本(返回[[String]]
)
tsvFile = endBy line newline
line = sepBy cell tab
cell = many (noneOf "\t\n")
我想更改它以支持这些类型:
data Cell = CellString String
| CellNumber Int
| CellBool Bool
deriving (Show)
以下是我为数字和布尔定义的函数。这些不正确吗?
cellBool = do
b <- oneOf "YN"
return $ CellBool (b == 'Y')
cellNumber = do
d <- many digit
return $ CellNumber (read d)
cellString = do
s <- many (noneOf "\t\n")
return $ CellString s
这就是我认为我需要做的才能让它发挥作用:
cell = cellBool <|> cellNumber <|> cellString
但它不起作用。在cellString返回Right []
之前运行cellNumber。如果我将cellString
放在列表中的第一位,它会将整个文件解析为字符串。
我确定我遗漏了一些基本的东西。比如,我认为只有cellString
方法正在处理标签分隔符,但我对parsec和混淆并不熟悉。感谢您的帮助!
答案 0 :(得分:2)
我只需更改cellNumber
:
cellNumber = do
d <- many1 digit
return $ CellNumber (read d)
问题在于cellNumber
因使用many
而读取空字符串。使用many1
表示解析器失败,允许cellString
执行。
但是,此时您的解析器会在"123a\n"
之类的输入上失败,因此您需要找出回溯以使其正常工作。
使用定义
cellNumber = do
d <- many1 digit
lookAhead $ oneOf "\t\n"
return $ CellNumber (read d)
可能并不理想。相反,我会考虑像
这样的东西cellNumber = do
d <- many1 digit
notFollowedBy cellString
return $ CellNumber (read d)
然后将您的cell
功能更改为
cell = try cellBool <|> try cellNumber <|> cellString
答案 1 :(得分:1)
首先,您应该按照
的顺序运行解析器parse (tsvFile <* eof) nm s
否则,Parsec将找到匹配的文件的最长前缀,并在第一次解析错误后丢弃每一行。
其次,您的某些文本字段是否可能以字符&#34; Y&#34;开头。或&#34; N&#34;还是用数字? Parsec只会对这些字段尝试cellBool
或cellNumber
,并且当他们不匹配时,整个解析都会失败。您可能希望在try
中包含这两个备选方案,以告诉Parsec如果在该字段的第一个字符后失败一段时间后继续下一个匹配:
cell = try cellBool <|> try cellNumber <|> cellString
-- Don't need try on cellString as it's the last alternative