module Duckling.Numeral.Helpers
( decimalsToDouble
, double
, integer
, multiply
, divide
, numberBetween
, numberWith
, oneOf
, parseDouble
, parseInt
, parseInteger
, withGrain
, withMultipliable
, parseDecimal,
) where
import qualified Data.Attoparsec.Text as Atto
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as Text
import Prelude
import Duckling.Dimensions.Types
import Duckling.Numeral.Types
import Duckling.Types hiding (value)
zeroT :: Text
zeroT = Text.singleton '0'
dot :: Text
dot = Text.singleton '.'
comma :: Text
comma = Text.singleton ','
parseInt :: Text -> Maybe Int
parseInt = (fromIntegral <$>) . parseInteger
parseInteger :: Text -> Maybe Integer
parseInteger =
either (const Nothing) Just . Atto.parseOnly (Atto.signed Atto.decimal)
parseDouble :: Text -> Maybe Double
parseDouble s
| Text.head s == '.' = go $ Text.append zeroT s
| otherwise = go s
where go = either (const Nothing) Just . Atto.parseOnly Atto.double
decimalsToDouble :: Double -> Double
decimalsToDouble x =
let xs = filter (\y -> x y < 0)
. take 10
. iterate (*10) $ 1 in
case xs of
[] -> 0
(multiplier : _) -> x / multiplier
numberWith :: (NumeralData -> t) -> (t -> Bool) -> PatternItem
numberWith f pred = Predicate $ \x ->
case x of
(Token Numeral x@NumeralData{}) -> pred (f x)
_ -> False
numberBetween :: Double -> Double -> PatternItem
numberBetween low up = Predicate $ \x ->
case x of
(Token Numeral NumeralData {value = v, multipliable = False}) ->
low <= v && v < up
_ -> False
oneOf :: [Double] -> PatternItem
oneOf vs = Predicate $ \x ->
case x of
(Token Numeral NumeralData {value = v}) -> elem v vs
_ -> False
withMultipliable :: Token -> Maybe Token
withMultipliable (Token Numeral x@NumeralData{}) =
Just . Token Numeral $ x {multipliable = True}
withMultipliable _ = Nothing
withGrain :: Int -> Token -> Maybe Token
withGrain g (Token Numeral x@NumeralData{}) =
Just . Token Numeral $ x {grain = Just g}
withGrain _ _ = Nothing
double :: Double -> Maybe Token
double x = Just . Token Numeral $ NumeralData
{ value = x
, grain = Nothing
, multipliable = False
}
integer :: Integer -> Maybe Token
integer = double . fromIntegral
multiply :: Token -> Token -> Maybe Token
multiply
(Token Numeral (NumeralData {value = v1}))
(Token Numeral (NumeralData {value = v2, grain = g})) = case g of
Nothing -> double $ v1 * v2
Just grain | v2 > v1 -> double (v1 * v2) >>= withGrain grain
| otherwise -> Nothing
multiply _ _ = Nothing
divide :: Token -> Token -> Maybe Token
divide
(Token Numeral (NumeralData {value = v1}))
(Token Numeral (NumeralData {value = v2})) = case v1 / v2 of
x | isInfinite x || isNaN x -> Nothing
x -> double x
divide _ _ = Nothing
parseDecimal :: Bool -> Text -> Maybe Token
parseDecimal isDot match
| isDot = parseDouble match >>= double
| otherwise =
parseDouble (Text.replace comma dot match)
>>= double