packages feed

fortran-src-0.5.0: src/Language/Fortran/Parser/Utils.hs

{-# LANGUAGE LambdaCase #-}

{-| Simple module to provide functions that read Fortran literals -}
module Language.Fortran.Parser.Utils
  ( readReal
  , readInteger
  , parseRealLiteral
  , RealLit(..)
  , Exponent(..)
  , NumSign(..)
  , ExponentLetter(..)
  ) where

import Language.Fortran.AST (Kind)

import Data.Char
import Numeric
import Text.Read (readMaybe)

breakAtDot :: String -> (String, String)
replaceDwithE :: Char -> Char
readsToMaybe :: [(a, b)] -> Maybe a
fixAtDot :: (String, String) -> (String, String)
fixAtDot' :: (String, String) -> (String, String)
combineAtDot :: (String, String) -> String

-- | Convert a Fortran literal Real into a Haskell Double.
readReal :: String -> Maybe Double
readReal = readsToMaybe . reads . filter (/= '+') . combineAtDot . fixAtDot . breakAtDot . map replaceDwithE . takeWhile (/= '_')

-- | Convert a Fortran literal Integer into a Haskell Integer.
readInteger :: String -> Maybe Integer
readInteger s = readsToMaybe $ case s' of
  'b':_ -> readInt 2 (`elem` "01") digitToInt (drop 2 s')
  'o':_ -> readInt 8 (`elem` ['0'..'7']) digitToInt (drop 2 s')
  'z':_ -> readInt 16 (`elem` (['0'..'9'] ++ ['A'..'F'] ++ ['a'..'f'])) digitToInt (drop 2 s')
  _     -> readSigned readDec s'
  where
    s' = filter (/= '+') . takeWhile (/= '_') $ s

fixAtDot' ("", r)                      = ("0", r)
fixAtDot' ("-", r)                     = ("-0", r)
fixAtDot' (l, "")                      = (l, "0")
fixAtDot' (l, r0:r) | not (isDigit r0) = (l, '0':r0:r)
fixAtDot' x                            = x

combineAtDot (a, b) = a ++ "." ++ b
fixAtDot x
  | x == x'         = x
  | otherwise       = fixAtDot x' where x' = fixAtDot' x
breakAtDot          = fmap (drop 1) . break (=='.')
replaceDwithE 'd'   = 'e'
replaceDwithE c     = c

readsToMaybe r = case r of
  (x, _):_ -> Just x
  _ -> Nothing

--------------------------------------------------------------------------------

-- TODO limitation
-- Kind params allow 'Name's as well (which are checked at compile time to be a
-- special type of constant expression). We limit ourselves to integer kind
-- params only, because currently we don't handle full kind params in the later
-- stages anyway.
type KindParam = Kind

-- | A REAL literal may have an optional exponent and kind.
--
-- The value can be retrieved as a 'Double' by using these parts.
data RealLit = RealLit
  { realLitValue     :: String -- xyz.abc, xyz, xyz., .abc
  , realLitExponent  :: Maybe Exponent
  , realLitKindParam :: Maybe KindParam
  } deriving (Eq, Ord, Show)

-- | An exponent is an exponent letter (E, D) and a value (with an optional
-- sign).
data Exponent = Exponent
  { expLetter :: ExponentLetter
  , expSign   :: Maybe NumSign
  , expNum    :: Int
  } deriving (Eq, Ord, Show)

-- Note: Some Fortran language references include extensions here. HP's F90
-- reference provides a Q exponent letter which sets kind to 16.
data ExponentLetter
  = ExpLetterD
  | ExpLetterE
    deriving (Eq, Ord, Show)

data NumSign
  = SignPos
  | SignNeg
    deriving (Eq, Ord, Show)

-- | Parse a Fortran literal real to its constituent parts.
parseRealLiteral :: String -> RealLit
parseRealLiteral r =
    RealLit { realLitValue     = takeWhile isValuePart r
            , realLitExponent  = parseRealLitExponent (dropWhile isValuePart r)
            , realLitKindParam = parseRealLitKindInt (dropWhile (/= '_') r)
            }
  where
    -- slightly ugly: we add the signs in here to allow -1.0 easily
    isValuePart :: Char -> Bool
    isValuePart ch
      | isDigit ch                 = True
      | ch `elem` ['.', '-', '+']  = True
      | otherwise                  = False
    parseRealLitKindInt :: String -> Maybe Kind
    parseRealLitKindInt = \case
      '_':chs -> readMaybe chs
      _       -> Nothing
    parseRealLitExponent :: String -> Maybe Exponent
    parseRealLitExponent "" = Nothing
    parseRealLitExponent (c:cs) = do
        letter <-
                case toLower c of
                  'e' -> Just ExpLetterE
                  'd' -> Just ExpLetterD
                  _   -> Nothing
        let (sign, cs'') =
                case cs of
                  ""       -> (Nothing, cs)
                  c':cs'  -> -- TODO: want to locally scope cs' but unsure how to??
                    case c' of
                      '-' -> (Just SignNeg, cs')
                      '+' -> (Just SignPos, cs')
                      _   -> (Nothing     , cs)
            digitStr = read (takeWhile isDigit cs'')
        return $ Exponent letter sign digitStr