2012-05-25 57 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'之前的行末尾。 –