deka-0.6.0.2: exposed/Deka/Native/FromString.hs
-- | Uses the specification for string conversions given in the
-- General Decimal Arithmetic Specification to convert strings to an
-- abstract syntax tree. The specification for string conversions
-- is at
--
-- <http://speleotrove.com/decimal/daconvs.html>
--
-- The functions and types in this module fall into two groups. The
-- first group converts a string to a 'NumericString', which is an
-- abstract representation of the grammar given in the General
-- Decimal Arithmetic Specification. These functions use Parsec to
-- parse the string. The second group transforms the
-- 'NumericString' to an 'A.Abstract', a form which more closely
-- aligns with the abstract representation given at
--
-- <http://speleotrove.com/decimal/damodel.html>.
--
-- You can transform an 'A.Abstract' to a numeric string; no
-- functions are provided to transform a 'NumericString' directly
-- back to a string.
module Deka.Native.FromString where
import Data.Char (toLower)
import Control.Applicative
import Text.Parsec.String
import Text.Parsec.Prim (tokenPrim, try, parse)
import Text.Parsec.Pos
import Text.Parsec.Char (char, string)
import Text.Parsec.Combinator (many1, eof)
import qualified Deka.Native.Abstract as A
import Deka.Native.Abstract
(Decem(..), Novem(..), decemListToInt)
import Deka.Dec (Sign(..))
import qualified Deka.Dec as D
import qualified Data.ByteString.Char8 as BS8
sign :: Parser Sign
sign = tokenPrim show next f
where
next pos c _ = updatePosChar pos c
f c = case c of
'-' -> Just Sign1
'+' -> Just Sign0
_ -> Nothing
optSign :: Parser Sign
optSign = do
s <- optional sign
return $ maybe Sign0 id s
digit :: Parser Decem
digit = tokenPrim show next f
where
next pos c _ = updatePosChar pos c
f c = case c of
{ '0' -> Just D0; '1' -> Just $ Nonem D1; '2' -> Just $ Nonem D2;
'3' -> Just $ Nonem D3; '4' -> Just $ Nonem D4;
'5' -> Just $ Nonem D5; '6' -> Just $ Nonem D6;
'7' -> Just $ Nonem D7; '8' -> Just $ Nonem D8;
'9' -> Just $ Nonem D9; _ -> Nothing }
indicator :: Parser ()
indicator = () <$ char 'e'
digits :: Parser [Decem]
digits = many1 digit
data DecimalPart
= WholeFrac [Decem] [Decem]
| WholeOnly [Decem]
deriving (Eq, Ord, Show)
decimalPart :: Parser DecimalPart
decimalPart = do
ds1 <- optional digits
case ds1 of
Nothing -> do
_ <- char '.'
fmap WholeOnly digits
Just ds -> do
dot <- optional (char '.')
case dot of
Just _ -> do
ds2 <- many digit
return $ WholeFrac ds ds2
Nothing -> return $ WholeOnly ds
data ExponentPart = ExponentPart
{ expSign :: Sign
, expDigits :: [Decem]
} deriving (Eq, Ord, Show)
exponentPart :: Parser ExponentPart
exponentPart = do
indicator
sgn <- optSign
ds <- digits
return $ ExponentPart sgn ds
infinity :: Parser ()
infinity = try $ do
_ <- string "inf"
_ <- optional (string "inity")
return ()
nanId :: Parser A.Noisy
nanId = try (string "nan" >> return A.Quiet)
<|> try (string "snan" >> return A.Signaling)
data NaN = NaN A.Noisy [Decem]
deriving (Eq, Ord, Show)
nan :: Parser NaN
nan = liftA2 NaN nanId (many digit)
data NumericValue
= NVDec DecimalPart (Maybe ExponentPart)
| Infinity
deriving (Eq, Ord, Show)
numericValue :: Parser NumericValue
numericValue =
(Infinity <$ infinity)
<|> liftA2 NVDec decimalPart (optional exponentPart)
data NumericString = NumericString
{ nsSign :: Sign
, nsValue :: Either NumericValue NaN
} deriving (Eq, Ord, Show)
numericString :: Parser NumericString
numericString = liftA2 NumericString optSign ei
where
ei = (fmap Left numericValue <|> fmap Right nan)
parseNumericString :: String -> Either String NumericString
parseNumericString s =
case parse (numericString <* eof) "" (map toLower s) of
Left e -> Left (show e)
Right g -> Right g
numericStringToAbstract :: NumericString -> A.Abstract
numericStringToAbstract (NumericString sgn ei) = A.Abstract sgn val
where
val = case ei of
Left nv -> case nv of
NVDec dp me -> uncurry A.Finite $ finiteToAbstract dp me
Infinity -> A.Infinite
Right nn -> A.NotANumber . nanToAbstract $ nn
nanToAbstract
:: NaN
-> A.NonNum
nanToAbstract (NaN nsy ds) = A.NonNum nsy . fmap A.Diagnostic
. A.decemListToDecuple $ ds
finiteToAbstract
:: DecimalPart
-> Maybe ExponentPart
-> (A.Coefficient, A.Exponent)
finiteToAbstract dp mep = (coe, ex)
where
ex = abstractExponent . actualExponent dp
. givenExponent $ mep
coe = abstractCoeff dp
-- | A numeric value for the exponent that was given in the input
-- string.
givenExponent :: Maybe ExponentPart -> Integer
givenExponent me = case me of
Nothing -> 0
Just (ExponentPart s ds) -> getSgn $ decemListToInt ds
where
getSgn = case s of
Sign0 -> id
Sign1 -> negate
-- | The number of digits after the decimal point, subtracted from
-- the numeric value for the exponent given in the string
actualExponent
:: DecimalPart
-> Integer
-- ^ Output from 'givenExponent'
-> Integer
actualExponent d i = case d of
WholeFrac _ ds -> i - fromIntegral (length ds)
_ -> i
-- The value of the abstract exponent.
abstractExponent
:: Integer
-- ^ The output from 'actualExponent'
-> A.Exponent
abstractExponent = A.Exponent . A.intToFirmado
abstractCoeff :: DecimalPart -> A.Coefficient
abstractCoeff d =
let ds = case d of
WholeFrac d1 d2 -> d1 ++ d2
WholeOnly d1 -> d1
in A.Coefficient $ A.decemListToAut ds
stringToAbstract
:: String
-- ^ Input string
-> Either String A.Abstract
-- ^ Returns a Right with the abstract representation of the input
-- string, if the input conformed to the numeric string
-- specification given in the General Decimal Arithmetic
-- Specification. Otherwise, returns a Left with an error
-- message.
stringToAbstract = fmap numericStringToAbstract . parseNumericString
-- | Transforms a 'Dec' to an 'Abstract'.
decToAbstract :: D.Dec -> A.Abstract
decToAbstract = either (error msg) id . stringToAbstract
. BS8.unpack . D.toByteString
where
msg = "decToAbstract: error: could not parse output from "
++ "toByteString"