{- | 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]