我正在使用Parsec解析器来处理有些复杂的数据文件格式(我无法控制这种格式)。
我已经取得了很大的进步,但我目前仍然坚持以下几点。
我需要能够像这样解析一行:
4 0.123 1.452 0.667 * 3.460 149 - -
从语义上讲,4
是nodeNum,Floats
和*
是负日志概率(因此,*
表示概率为零的负对数)。 149
和减号都是垃圾,我可以丢弃,但我需要至少确保它们不会破坏解析器。
这是我到目前为止所拥有的:
这处理"垃圾"我提到。它可能更简单,但它本身就有效。
emAnnotationSet = (,,) <$> p_int <*>
(reqSpaces *> char '-') <*>
(reqSpaces *> char '-')
行开头的nodeNum
由另一个有效的解析器处理,我无需进入。
问题在于尝试从行中挑选出所有p_logProb
,而不使用emAnnotationSet
开头的数字。
p_logProb
的解析器如下所示:
p_logProb = liftA mkScore (lp <?> "logProb")
where lp = try dub <|> string "*"
dub = (++) <$> ((++) <$> many1 digit <*> string ".") <*> many1 digit
最后,我尝试将logProb
条目与尾随emAnnotationSet
(以整数开头)分开,如下所示:
hmmMatchEmissions = optSpaces *> (V.fromList <$> sepBy p_logProb reqSpaces)
<* optSpaces <* emAnnotationSet <* eol
<?> "matchEmissions"
因此,p_logProb
只会在以数字开头的浮点数上成功,包括一个小数点,然后有更多的数字(这个限制受到文件格式的尊重)。
我希望try
定义中的p_logProb
可以避免使用前导数字,如果它没有解析小数和其他数字,但是这并没有。似乎工作; Parsec仍然抱怨它在emAnnotationSet
中的整数数字之后看到一个意外的空格:
Left "hmmNode" (line 1, column 196):
unexpected " "
expecting logProb
列196对应于减号之前的整数之后的空格,因此我很清楚问题是p_logProb
解析器正在使用整数。我如何解决这个问题,以便p_logProb
解析器正确使用前瞻,从而为emAnnotationSet
解析器留下输入?
答案 0 :(得分:2)
终止概率的整数不能被误认为概率,因为它不包含小数点。 lexeme
组合子将解析器转换为跳过尾随空格的解析器。
import Text.Parsec
import Text.Parsec.String
import Data.Char
import Control.Applicative ( (<$>), (<*>), (<$), (<*), (*>) )
fractional :: Fractional a => Parser a
fractional = try $ do
n <- fromIntegral <$> decimal
char '.'
f <- foldr (\d f -> (f + fromIntegral (digitToInt d))/10.0) 0.0 <$> many1 digit
return $ n + f
decimal :: Parser Int
decimal = foldl (\n d -> 10 * n + digitToInt d) 0 <$> many1 digit
lexeme :: Parser a -> Parser a
lexeme p = p <* skipMany (char ' ')
data Row = Row Int [Maybe Double]
deriving ( Show )
probability :: Fractional a => Parser (Maybe a)
probability = (Just <$> fractional) <|> (Nothing <$ char '*')
junk = lexeme decimal <* count 2 (lexeme $ char '-')
row :: Parser Row
row = Row <$> lexeme decimal <*> many1 (lexeme probability) <* junk
rows :: Parser [Row]
rows = spaces *> sepEndBy row (lexeme newline) <* eof
<强>用法:强>
*Main> parseTest rows "4 0.123 1.234 2.345 149 - -\n5 0.123 * 2.345 149 - -"
[Row 4 [Just 0.123,Just 1.234,Just 2.345],Row 5 [Just 0.123,Nothing,Just 2.345]]
答案 1 :(得分:1)
我不确定你的问题。但是,要根据您的描述解析给定的行,使用Text.Parsec.Token1
中定义的现有词法分析器并将它们连接在一起会容易得多。
以下代码将该行解析为Line数据类型,如有必要,您可以从那里进一步处理它。它不是在解析之前尝试过滤掉-
和整数,而是使用parseEntry
解析器,如果它是Float值Just Double
,则返回Just 0
*
对于整数和破折号,我只有Nothing
。然后使用catMaybes
非常简单地过滤。
以下是代码:
module Test where
import Text.Parsec
import qualified Text.Parsec.Token as P
import Text.Parsec.Language (haskellDef)
import Control.Applicative ((<$>))
import Data.Maybe (catMaybes)
lexer = P.makeTokenParser haskellDef
parseFloat = P.float lexer
parseInteger = P.natural lexer
whiteSpace = P.whiteSpace lexer
parseEntry = try (Just <$> parseFloat)
<|> try (const (Just 0) <$> (char '*' >> whiteSpace))
<|> try (const Nothing <$> (char '-' >> whiteSpace))
<|> (const Nothing <$> parseInteger)
data Line = Line {
lineNodeNum :: Integer
, negativeLogProbabilities :: [Double]
} deriving (Show)
parseLine = do
nodeNum <- parseInteger
whiteSpace
probabilities <- catMaybes <$> many1 parseEntry
return $ Line { lineNodeNum = nodeNum, negativeLogProbabilities = probabilities }
使用示例:
*Test> parseTest parseLine "4 0.123 1.452 0.667 * 3.460 149 - -"
Line {lineNodeNum = 4, negativeLogProbabilities = [0.123,1.452,0.667,0.0,3.46]}
可能(或可能不是)成为问题的唯一问题是它将*-
解析为两个不同的标记,而不是在解析时失败。例如
*Test> parseTest parseLine "4 0.123 1.452 0.667 * 3.460 149 - -*"
Line {lineNodeNum = 4, negativeLogProbabilities = [0.123,1.452,0.667,0.0,3.46,0.0]}
请注意日志概率末尾的额外0.0
。