fortran-src-0.7.0: src/Language/Fortran/AST/RealLit.hs
{- |
Supporting code for handling Fortran REAL literals.
Fortran REAL literals have some idiosyncrasies that prevent them from lining up
with Haskell's reals immediately. So, we parse into an intermediate data type
that can be easily exported with full precision later. Things we do:
* Strip explicit positive signs so that signed values either begin with the
minus sign @-@ or no sign. ('Read' doesn't allow explicit positive signs.)
* Make exponent explicit by adding the default exponent @E0@ if not present.
* Make implicit zeroes explicit. @.123 -> 0.123@, @123. -> 123.0@. (Again,
Haskell literals do not support this.)
-}
{-# LANGUAGE DeriveDataTypeable, DeriveGeneric, DeriveAnyClass #-}
{-# LANGUAGE RecordWildCards, LambdaCase #-}
module Language.Fortran.AST.RealLit where
import qualified Data.Char as Char
import GHC.Generics
import Data.Data
import Control.DeepSeq ( NFData )
import Text.PrettyPrint.GenericPretty ( Out )
-- | A Fortran real literal. (Does not include the optional kind parameter.)
--
-- A real literal is formed of a signed rational significand, and an 'Exponent'.
--
-- See F90 ISO spec pg.27 / R412-416.
--
-- Note that we support signed real literals, even though the F90 spec indicates
-- non-signed real literals are the "default" (signed are only used in a "spare"
-- rule). Our parsers should parse explicit signs as unary operators. There's no
-- harm in supporting signed literals though, especially since the exponent *is*
-- signed.
data RealLit = RealLit
{ realLitSignificand :: String
-- ^ A string representing a signed decimal.
-- ^ Approximate regex: @-? ( [0-9]+ \. [0-9]* | \. [0-9]+ )@
, realLitExponent :: Exponent
} deriving (Eq, Show, Data, Typeable, Generic, NFData, Out, Ord)
-- | An exponent is an exponent letter (E, D) and a signed integer.
data Exponent = Exponent
{ exponentLetter :: ExponentLetter
, exponentNum :: String
} deriving (Eq, Show, Data, Typeable, Generic, NFData, Out, Ord)
-- Note: Some Fortran language references include extensions here. HP's F90
-- reference provides a Q exponent letter which sets kind to 16.
data ExponentLetter
= ExpLetterE -- ^ KIND=4 (float)
| ExpLetterD -- ^ KIND=8 (double)
| ExpLetterQ -- ^ KIND=16 ("quad", rare? extension)
deriving (Eq, Show, Data, Typeable, Generic, NFData, Out, Ord)
-- | Prettify a 'RealLit' in a Haskell-compatible way.
prettyHsRealLit :: RealLit -> String
prettyHsRealLit r = realLitSignificand r <> "e" <> exponentNum (realLitExponent r)
readRealLit :: (Fractional a, Read a) => RealLit -> a
readRealLit = read . prettyHsRealLit
-- UNSAFE. Expects a valid Fortran REAL literal.
parseRealLit :: String -> RealLit
parseRealLit r =
let (significandStr, exponentStr) = span isSignificand r
realLitExponent = parseExponent exponentStr
realLitSignificand = normalizeSignificand (stripPositiveSign significandStr)
in RealLit{..}
where
-- | Ensure that the given decimal string is in form @x.y@.
normalizeSignificand str = case span (/= '.') str of
([], d) -> '0':d -- .456
(i, ".") -> i<>".0" -- 123.
(i, "") -> i<>".0" -- 123
_ -> str -- 123.456
parseExponent "" = Exponent { exponentLetter = ExpLetterE, exponentNum = "0" }
parseExponent (l:str) =
let exponentLetter = parseExponentLetter l
exponentNum = stripPositiveSign str
in Exponent{..}
stripPositiveSign = \case
[] -> []
c:s -> case c of
'+' -> s
_ -> c:s
isSignificand ch | Char.isDigit ch = True
| ch `elem` ['.', '-', '+'] = True
| otherwise = False
parseExponentLetter ch = case Char.toLower ch of
'e' -> ExpLetterE
'd' -> ExpLetterD
'q' -> ExpLetterQ
_ -> error $ "Language.Fortran.AST.RealLit.parseRealLit: invalid exponent letter: " <> [ch]