『Haskell入門』に日時表現("YYYY/MM/DD hh:mm:ss"
)のパーサをAttoparsecで作る節*1があり、
このままでは9999/99/99 99:99:99のような入力ができてしまいますが、月の入力範囲を1~12に制限するといった制約をかけることも、これまで説明した範囲で簡単に実現できます
という記述があったので、値の入力範囲に制限があるバージョンを書いてみた。具体的にはguard
を使う*2。
{-# LANGUAGE OverloadedStrings #-} import qualified Data.Text as T import Control.Monad import Data.Attoparsec.Text hiding (take) data YMD = YMD Int Int Int deriving Show data HMS = HMS Int Int Int deriving Show countRead :: Read a => Int -> Parser Char -> Parser a countRead i = fmap read . count i year :: Parser Int year = countRead 4 digit month :: Parser Int month = do month <- countRead 2 digit guard $ 1 <= month && month <= 12 return month day :: Parser Int day = do day <- countRead 2 digit guard $ 1 <= day && day <= 31 return day hour :: Parser Int hour = do hour <- countRead 2 digit guard $ 0 <= hour && hour <= 23 return hour minute :: Parser Int minute = do minute <- countRead 2 digit guard $ 0 <= minute && minute <= 59 return minute second :: Parser Int second = do second <- countRead 2 digit guard $ 0 <= second && second <= 59 return second ymdParser :: Parser YMD ymdParser = YMD <$> year <* char '/' <*> month <* char '/' <*> day hmsParser :: Parser HMS hmsParser = HMS <$> hour <* char ':' <*> minute <* char ':' <*> second dateTimeParser :: Parser (YMD, HMS) dateTimeParser = (,) <$> ymdParser <* char ' ' <*> hmsParser
各パーサ関数でControl.Monadのguard
に年、月、日、時、分、秒それぞれが満たすべき条件式を渡す。パースした結果得られた整数がguard
に渡した条件を満たさないとき、結果がempty
になる。
Parser
はAlternative
のインスタンスであり、empty
を返した時点でfail "empty"
が実行される。この時点でパースが中止される。
それぞれの関数を組み合わせることでApplicativeスタイルでパーサを表現できている。
上のコードを読み込んだ状態で次のコードを実行する:
main :: IO () main = do print $ parse dateTimeParser "2020/03/01 12:34:56" `feed` "" print $ parse dateTimeParser "2020/03/32 12:34:56" `feed` ""
このとき結果は次のとおりで、invalidな値を渡すとパースに失敗する:
Done "" (YMD 2020 3 1,HMS 12 34 56) Fail " 12:34:56" [] "Failed reading: empty"