summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <aethoago@141.li>2017-02-16 19:03:30 +0100
committerGregor Kleen <aethoago@141.li>2017-02-16 19:03:30 +0100
commit4343e02ca8431e61e2dc1755d1288dd6c55c9a23 (patch)
tree2f1397eb4c8649dffb7ae2adaedfd101bdda4fab
parent333ea946916b005134e7ba249178acad4858a67d (diff)
downloadpostdelay-4343e02ca8431e61e2dc1755d1288dd6c55c9a23.tar
postdelay-4343e02ca8431e61e2dc1755d1288dd6c55c9a23.tar.gz
postdelay-4343e02ca8431e61e2dc1755d1288dd6c55c9a23.tar.bz2
postdelay-4343e02ca8431e61e2dc1755d1288dd6c55c9a23.tar.xz
postdelay-4343e02ca8431e61e2dc1755d1288dd6c55c9a23.zip
Bounded numeric parsers
-rw-r--r--lib/Postdelay/TimeSpec.hs7
-rw-r--r--lib/Postdelay/TimeSpec/Units.hs9
-rw-r--r--lib/Postdelay/TimeSpec/Utils.hs69
-rw-r--r--postdelay.cabal1
-rw-r--r--postdelay.nix12
5 files changed, 83 insertions, 15 deletions
diff --git a/lib/Postdelay/TimeSpec.hs b/lib/Postdelay/TimeSpec.hs
index 384de4b..edd70c1 100644
--- a/lib/Postdelay/TimeSpec.hs
+++ b/lib/Postdelay/TimeSpec.hs
@@ -56,13 +56,18 @@ timeSpec = label "Relative time specification" $
56 ] 56 ]
57 57
58 toEndo :: [RWS LocalTime () LocalTime a] -> Endo LocalTime 58 toEndo :: [RWS LocalTime () LocalTime a] -> Endo LocalTime
59 toEndo acts = Endo $ \t -> fst $ execRWS (sequence acts) t t 59 toEndo (sequence -> act) = Endo $ \t -> fst $ execRWS act t t
60 60
61timeOfDay, dateSpec :: StringParser s m => m (RWS LocalTime () LocalTime ()) 61timeOfDay, dateSpec :: StringParser s m => m (RWS LocalTime () LocalTime ())
62timeOfDay = label "Time of day" $ shiftBack (1 % Day) . assign time <$> choice 62timeOfDay = label "Time of day" $ shiftBack (1 % Day) . assign time <$> choice
63 [ TimeOfDay 0 0 0 <$ string' "midnight" 63 [ TimeOfDay 0 0 0 <$ string' "midnight"
64 , TimeOfDay 12 0 0 <$ string' "noon" 64 , TimeOfDay 12 0 0 <$ string' "noon"
65 , TimeOfDay 16 0 0 <$ string' "teatime" 65 , TimeOfDay 16 0 0 <$ string' "teatime"
66 , try $ do
67 h <- boundedNatural True $ 0 <=..<= 24
68 m <- boundedNatural True $ 0 <=..<= 59
69 s <- option 0 $ boundedRational True $ 0 <=..< 61
70 return $ TimeOfDay h m s
66 ] 71 ]
67dateSpec = label "Date" $ (date <~) <$> choice 72dateSpec = label "Date" $ (date <~) <$> choice
68 [ view date <$ string' "today" 73 [ view date <$ string' "today"
diff --git a/lib/Postdelay/TimeSpec/Units.hs b/lib/Postdelay/TimeSpec/Units.hs
index a094ea3..4874ce9 100644
--- a/lib/Postdelay/TimeSpec/Units.hs
+++ b/lib/Postdelay/TimeSpec/Units.hs
@@ -56,14 +56,7 @@ instance HasResolution p => VectorSpace (Fixed p) where
56 56
57 57
58timeLength :: StringParser s m => m Time 58timeLength :: StringParser s m => m Time
59timeLength = (*^) <$> lexeme rational <*> timeUnit <?> "Length of time" 59timeLength = (*^) <$> lexeme (boundedRational False $ 0 <=..< PosInf) <*> timeUnit <?> "Length of time"
60
61rational :: forall s m n. (StringParser s m, Fractional n, Num n) => m n
62rational = combine <$> some digitChar <*> option [] (char '.' *> many digitChar) <?> "Decimal number"
63 where
64 combine :: [Char] -> [Char] -> n
65 combine (map fromDigit -> whole) (map fromDigit -> fractional)
66 = foldl' (\i l -> i * 10 + l) 0 whole + (foldr' (\h t -> h + t / 10) 0 fractional) / 10
67 60
68timeUnit :: StringParser s m => m Time 61timeUnit :: StringParser s m => m Time
69timeUnit = label "Unit of time" . choice $ 62timeUnit = label "Unit of time" . choice $
diff --git a/lib/Postdelay/TimeSpec/Utils.hs b/lib/Postdelay/TimeSpec/Utils.hs
index 54b85f7..c10fc4b 100644
--- a/lib/Postdelay/TimeSpec/Utils.hs
+++ b/lib/Postdelay/TimeSpec/Utils.hs
@@ -1,6 +1,17 @@
1{-# LANGUAGE RankNTypes, ConstraintKinds, TypeFamilies, FlexibleContexts, ScopedTypeVariables, ViewPatterns, TupleSections #-} 1{-# LANGUAGE RankNTypes, ConstraintKinds, TypeFamilies, FlexibleContexts, ScopedTypeVariables, ViewPatterns, TupleSections #-}
2 2
3module Postdelay.TimeSpec.Utils where 3module Postdelay.TimeSpec.Utils
4 ( StringParser
5 , spaceConsumer, spaces
6 , lexeme
7 , signed, optSigned
8 , sign
9 , boundedNatural
10 , boundedRational
11 , module Data.Interval
12 , fromDigit
13 , mkGramSepBy
14 ) where
4 15
5import Control.Applicative 16import Control.Applicative
6import Control.Monad 17import Control.Monad
@@ -10,8 +21,13 @@ import Data.Time
10import Data.Time.Lens 21import Data.Time.Lens
11import Data.Time.Zones 22import Data.Time.Zones
12 23
24import Data.Bool
25import Data.Foldable
13import Data.Functor 26import Data.Functor
27import Data.Function
14import Data.AdditiveGroup 28import Data.AdditiveGroup
29import Data.Interval (Interval, Extended(..), (<=..<=), (<=..<), (<..<=), (<..<), interval)
30import qualified Data.Interval as I
15 31
16import Text.Megaparsec 32import Text.Megaparsec
17import Text.Megaparsec.Prim (MonadParsec) 33import Text.Megaparsec.Prim (MonadParsec)
@@ -37,6 +53,57 @@ sign = label "sign" $ choice [ char '+' $> id
37 , char '-' $> negateV 53 , char '-' $> negateV
38 ] 54 ]
39 55
56boundedNatural :: forall s n m. (Show n, Real n, StringParser s m)
57 => Bool -- ^ Require number to be padded with zeroes?
58 -> Interval n -> m n
59boundedNatural padded (I.intersection (0 <=..< PosInf) -> bounds) = do
60 n <- foldl' (\i l -> i * 10 + l) 0 <$> digitN (fromDigit <$> digitChar) <?> "Natural number cotained in: " ++ show bounds
61 when (not $ n `I.member` bounds) $ fail "Natural number out of bounds"
62 return n
63 where
64 digitN :: m n -> m [n]
65 digitN p
66 | PosInf <- maxDigits
67 , Finite min <- minDigits = (++) <$> count min p <*> many p
68 | Finite max <- maxDigits
69 , Finite min <- minDigits
70 , not padded = count' min max p
71 | Finite max <- maxDigits
72 , Finite _ <- minDigits
73 , padded = count max p
74 | otherwise = error "boundedNatural in undefined state"
75
76 minDigits, maxDigits :: Extended Int
77 (minDigits, maxDigits) = ( fmap digits . I.lowerBound $ close bounds
78 , fmap digits . I.upperBound $ close bounds
79 )
80 where
81 close int
82 | (Finite min, False) <- I.lowerBound' int = close $ interval (Finite $ min + 1, True) (I.upperBound' int)
83 | (Finite max, False) <- I.upperBound' int = close $ interval (I.lowerBound' int) (Finite $ max - 1, True)
84 | otherwise = int
85
86 digits :: n -> Int
87 digits = ceiling . (logBase 10 :: Double -> Double) . realToFrac . abs
88
89boundedRational :: forall s m n. (Show n, RealFrac n, StringParser s m)
90 => Bool -- ^ Require number to be padded with zeroes
91 -> Interval n -> m n
92boundedRational padded (I.intersection (0 <=..< PosInf) -> bounds)
93 = (+) <$> boundedNatural padded wholeBounds <*> fractional <?> "Nonnegative real contained in: " ++ show bounds
94 where
95 fractional :: m n
96 fractional = reqFractional $ char '.' $> (/ 10) . foldr' (\h t -> h + t / 10) 0 <*> some (fromDigit <$> digitChar)
97
98 reqFractional
99 | contained = option 0
100 | otherwise = id
101 where (_, contained) = I.lowerBound' bounds
102 wholeBounds
103 | (Finite max, False) <- I.upperBound' bounds
104 , max == (fromInteger $ round max) = interval (I.lowerBound' bounds) (Finite $ max - 1, True)
105 | otherwise = bounds
106
40fromDigit :: Num n => Char -> n 107fromDigit :: Num n => Char -> n
41fromDigit c = fromIntegral $ fromEnum c - fromEnum '0' 108fromDigit c = fromIntegral $ fromEnum c - fromEnum '0'
42 109
diff --git a/postdelay.cabal b/postdelay.cabal
index 6d4323a..b184ce4 100644
--- a/postdelay.cabal
+++ b/postdelay.cabal
@@ -38,6 +38,7 @@ library
38 , exceptions >=0.8 && <1 38 , exceptions >=0.8 && <1
39 , lens >=4.15 && <5 39 , lens >=4.15 && <5
40 , lens-datetime >=0.3 && <1 40 , lens-datetime >=0.3 && <1
41 , data-interval >=1.2 && <2
41 hs-source-dirs: lib 42 hs-source-dirs: lib
42 default-language: Haskell2010 43 default-language: Haskell2010
43 44
diff --git a/postdelay.nix b/postdelay.nix
index 938557f..4c1dca6 100644
--- a/postdelay.nix
+++ b/postdelay.nix
@@ -1,6 +1,7 @@
1{ mkDerivation, base, case-insensitive, exceptions, hsemail, lens 1{ mkDerivation, base, case-insensitive, data-interval, exceptions
2, lens-datetime, list-t, megaparsec, mime, mtl, old-time, parsec 2, hsemail, lens, lens-datetime, list-t, megaparsec, mime, mtl
3, stdenv, time, transformers, tz, units, units-defs 3, old-time, parsec, stdenv, time, transformers, tz, units
4, units-defs
4}: 5}:
5mkDerivation { 6mkDerivation {
6 pname = "postdelay"; 7 pname = "postdelay";
@@ -9,8 +10,9 @@ mkDerivation {
9 isLibrary = true; 10 isLibrary = true;
10 isExecutable = true; 11 isExecutable = true;
11 libraryHaskellDepends = [ 12 libraryHaskellDepends = [
12 base case-insensitive exceptions hsemail lens lens-datetime list-t 13 base case-insensitive data-interval exceptions hsemail lens
13 megaparsec mime mtl old-time parsec time tz units units-defs 14 lens-datetime list-t megaparsec mime mtl old-time parsec time tz
15 units units-defs
14 ]; 16 ];
15 executableHaskellDepends = [ base transformers ]; 17 executableHaskellDepends = [ base transformers ];
16 homepage = "https://git.yggdrasil.li/gkleen/pub/postdelay"; 18 homepage = "https://git.yggdrasil.li/gkleen/pub/postdelay";