2012-05-25 24 views
1

我正在使用一个Parsec分析器来处理一个有点复杂的数据文件格式(我无法控制这种格式)。Parsec lookahead来处理整数

我已经取得了很多进展,但我目前坚持以下几点。

我需要能够在一定程度上解析这样一行:

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只会在以数字开头,包含一个小数点,然后有更多数字(此限制由文件格式所规定)的浮点数上成功。

我希望p_logProb定义中的try避免使用前导数字,如果它不解析小数和其余的,但这似乎不工作;秒差距仍然抱怨说,它是整数的emAnnotationSet数字后看到一个意想不到的空间:

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

列196对应于减号之前的整数后的空间,所以这是很清楚,我的问题是,分析器正在使用该整数。我该如何解决这个问题,以便p_logProb解析器正确地使用looka,从而为emAnnotationSet解析器留下输入?

回答

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]] 
+0

我想我可以得到我需要的东西。这些星号实际上不应该变成什么;他们和双打实际上已经成为Score的实例,但我已经有了构造函数,所以它应该是一个微不足道的变化。谢谢! –

1

我不完全确定你的问题。但是,要根据您的描述解析给定的行,使用Text.Parsec.Token1中定义的现有词法分析器会更容易,并将它们结合在一起。

下面的代码将行解析为Line数据类型,如有必要,您可以从那里进一步处理它。在解析之前,它不使用-和整数,而是使用parseEntry解析器,如果它是Float值则返回Just Double,对于整数和破折号则返回和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在数概率结束。

+0

嗯...问题是, '*' 不应该被解析为0.0;它是0的_negative log_,所以它实际上是无穷大的(被视为'negLogZero')。而且,从列表的末尾剥离一个无关的'0.0'看起来真的很不幸......我也在使用现有的词法分析器,因为这种文件格式的浮点数实际上并不是实际上任意的浮点数(例如,它们不能是否定的,电子记号是不允许的)。 –

+0

真的,我需要解析一堆“*”或使用浮点数的符号,十进制符号,然后是一个整数(然后是两个减号)。这就是我卡住的地方。 –

+0

啊,我误解了你关于额外0.0的观点。不,我并不担心' - *'出现。我仍然担心使用词法分析器。我不希望在允许的双打和'*'列表中允许任意整数。为了澄清,int和minus符号总是在'eol'之前的行末尾。 –