1

我正在使用 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从行中挑选出所有 s ,而不消耗 . 开头的数字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中的 thep_logProb可以避免使用前导数字,如果它不解析小数和其余部分,但这似乎不起作用;Parsec 仍然抱怨它在以下整数的数字之后看到了一个意外的空格emAnnotationSet

Left "hmmNode" (line 1, column 196):
unexpected " "
expecting logProb

第 196 列对应于减号之前的整数之后的空格,所以我很清楚问题在于p_logProb解析器正在使用整数。我该如何解决这个问题,以便p_logProb解析器正确使用前瞻,从而将输入留给emAnnotationSet解析器?

4

2 回答 2

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]]
于 2012-05-25T08:40:06.553 回答
1

我不太确定你的问题。Text.Parsec.Token1但是,要根据您的描述解析给出的行,使用现有的词法分析器定义 in并将它们连接在一起会容易得多。

下面的代码将该行解析为 Line 数据类型,如果需要,您可以从那里进一步处理它。它不是在解析之前尝试过滤掉-和 整数,而是使用一个parseEntry解析器,Just Double如果它是浮点值,则返回 a ,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请注意日志概率末尾的额外内容。

于 2012-05-25T04:58:31.573 回答