2016-08-20 11 views
1

パーサーでIntsとfloatを区別しようとしています。私は各intとフロートのための2つのパーサーを持っています。しかし、私は '。'で失敗するのに苦労しています。私は否定を探して先を見て、そして果物を得るように見えなかった。Parsecそれが見つかった場合にエラーが発生しました

私は質問が重複しないことを願っています。

私は、 'ではない次の文字を見て作業していました。それは醜い解決策です。

EDIT:コードを追加しました。

--Int-------------------------------------------------------------------- 
findInt :: Parser String 
findInt = plus <|> minus <|> number 

number :: Parser String 
number = many1 digit 

plus :: Parser String 
plus = char '+' *> number 

minus :: Parser String 
minus = char '-' <:> number 

makeInt :: Parser Int 
makeInt = prepareResult (findInt <* many (noneOf ".") <* endOfLine) 
    where readInt = read :: String -> Int 
      prepareResult = liftA readInt 
makeInt2 :: Parser Int 
makeInt2 = do 
    numberFound <- (findInt <* many (noneOf ".") <* endOfLine) 
    match <- char '.' 
    return (prepareResult numberFound) 
    where readInt = read :: String -> Int 
     prepareResult = readInt 
--End Int---------------------------------------------------------------- 
+0

'findInt'はどのように見えますか? – Alec

+0

追加されました。申し訳ありませんが、私はコードを書くときには考えていませんでした。 –

答えて

2

私はあなたが実際には1に2つのパーサを組み合わせるオフ最高だと思います。このような何かを試してみてください:

import Text.Parsec.String (Parser) 
import Control.Applicative ((<|>)) 
import Text.Parsec.Char (char,digit) 
import Text.Parsec.Combinator (many1,optionMaybe) 

makeIntOrFloat :: Parser (Either Int Float) 
makeIntOrFloat = do 
    sign <- optionMaybe (char '-' <|> char '+') 
    n <- many1 digit 
    m <- optionMaybe (char '.' *> many1 digit) 
    return $ case (m,sign) of 
     (Nothing, Just '-') -> Left (negate (read n)) 
     (Nothing, _)  -> Left (read n) 
     (Just m, Just '-') -> Right (negate (read n + read m/10.0^(length m))) 
     (Just m, _)   -> Right (read n + read m/10.0^(length m)) 

ErikRが正しい解決策を持っていますが、tryの使用はparsecは(ビット非効率的である)、バックトラックの可能性を追跡する必要があることを意味するときは、この中に不要であるという事実で場合。

ここで、重要な違いは、我々は実際に我々がフロートを持っているか、いない場合は、すぐに伝えることができるということである - ので、そこに、私たちはフロートを持っていない場合は、optionMaybechar '.' *> many1 digitパーサは(入力を消費することなく)すぐに失敗しますバックトラックを考慮する必要はありません。

GHCiの

ghci> import Text.Parsec.Prim 
ghci> parseTest makeIntOrFloat "1234.012" 
Right 1234.012 
ghci> parseTest makeIntOrFloat "1234" 
Left 1234 
1

私が使用しnotFollowedBy - 例えば:

import Text.Parsec 
import Text.Parsec.String 
import Text.Parsec.Combinator 

int :: Parser String 
int = many1 digit <* notFollowedBy (char '.') 

float :: Parser (String,String) 
float = do whole <- many1 digit 
      fracpart <- try (char '.' *> many digit) <|> (return "") 
      return (whole, fracpart) 

intOrFloat :: Parser (Either String (String,String)) 
intOrFloat = try (fmap Left int) <|> (fmap Right float) 

test1 = parseTest (intOrFloat <* eof) "123" 
test2 = parseTest (intOrFloat <* eof) "123.456" 
test3 = parseTest (intOrFloat <* eof) "123." 
0

で、それはあなたのパーサを構築するために応用的コンビネータを使用することが一般的に最も簡単である - これは、について推論するためにあなたのパーサーが容易になり、多くの場合、あなたはパーサのモナドとバックトラック機能を必要としません。

例えば、整数のパーサは、のように書くことができる:(「A」別の整数が続く)

import Text.Parsec hiding ((<|>), optional) 
import Text.Parsec.String 
import Numeric.Natural 
import Control.Applicative 
import Data.Foldable 

natural :: Parser Natural 
natural = read <$> many1 digit 

sign :: Num a => Parser (a -> a) 
sign = asum [ id <$ char '+' 
      , negate <$ char '-' 
      , pure id 
      ] 

integer :: Parser Integer 
integer = sign <*> (fromIntegral <$> natural) 

進数は、任意で小数部が続く整数であり、それ自体が何の小数部がない場合、その後、Integerを生成し、そこにある場合、INTEGを追加することにより、Doubleを作る - 適切な数は、ので、あなたのパーサは

decimalPart :: Parser Double 
decimalPart = read . ("0."++) <$> (char '.' *> many1 digit) 

integerOrDecimal :: Parser (Either Integer Double) 
integerOrDecimal = liftA2 cmb integer (optional decimalPart) where 
    cmb :: Integer -> Maybe Double -> Either Integer Double 
    cmb x Nothing = Left x 
    cmb x (Just d) = Right (fromIntegral x + d) 

cmbの定義は明らかであるように書くことができます。小数点以下を切り捨てます。

また、上記の点に小数のためのパーサを定義することができる:

decimal :: Parser Double 
decimal = either fromIntegral id <$> integerOrDecimal 

上記パーサのいずれも直接モナド関数を使用しないことに注意してください(すなわち>>=)またはバックトラッキングを - それは、シンプルで効率的にします。

関連する問題