Parsec预测处理整数

时间:2012-05-25 03:56:47

标签: haskell parsec

我正在使用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解析器留下输入?

2 个答案:

答案 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