fortran-vars-0.1.0: src/Language/Fortran/Vars/BozConstant.hs
module Language.Fortran.Vars.BozConstant
( resolveBozConstant
, bozToInt
, bozToInt2
, bozToInt4
, bozToInt8
)
where
import Data.Char ( digitToInt
, intToDigit
, toLower
)
import qualified Data.Map as M
import Numeric ( readInt
, showIntAtBase
)
import Text.Read ( ReadS )
import Language.Fortran.Vars.Types
( SymbolTableEntry(..)
, Type(..)
, SemType(..)
, Kind
, ExpVal(..)
, SymbolTable
)
-- | BozDecomposed is constructed with:
-- - String that represents the BOZ constant as it is in code, e.g. "'1111'x"
-- - String that represents digits without number system and lowercased, i.e. "ff1e" in "'Ff1E'x"
-- - Char that represents number system, i.e. 'x' in "'1111'x"
-- - Integer that represents number system, i.e. 16 in "'1111'x"
-- - String that represents binary translation of BOZ constant as it is.
-- It doesn't take into account any truncations nor overflows
data BozDecomposed = BozDecomposed String String Char Int String
deriving Show
parseBozDecomposed :: ExpVal -> BozDecomposed
parseBozDecomposed (Boz bozStr) = BozDecomposed bozStr
digits
numsysChar
numsysInt
binary
where
digits = getDigits bozStr
numsysChar = if head bozStr `elem` "boxz" then head bozStr else last bozStr
numsysInt = case numsysChar of
'b' -> 2
'o' -> 8
'x' -> 16
'z' -> 16
_ -> error
(numsysChar
: " is not supported BOZ specifier.\
\ Invalid fortran syntax"
)
binary = toBinaryString digits numsysInt
parseBozDecomposed _ = error "ExpVal is not a BOZ constant"
resolveBozConstant' :: SymbolTable -> String -> BozDecomposed -> ExpVal
resolveBozConstant' symTable assignSymbol bozDecomposed =
let entry = M.lookup assignSymbol symTable
in
case entry of
Just (SVariable (TInteger kind) _) ->
resolveBozConstantInContext bozDecomposed kind
Just (SVariable ty _) ->
error
$ assignSymbol
<> " is a "
<> show ty
<> "\nBOZ constants can only be resolved in an INTEGER context"
Just _ -> error
(assignSymbol
++ " is not a scalar variable. \
\Invalid fortran sytax"
)
Nothing -> error
(assignSymbol
++ " could not be found. \
\Invalid fortran syntax"
)
resolveBozConstantInContext :: BozDecomposed -> Kind -> ExpVal
resolveBozConstantInContext (BozDecomposed _ _ _ _ binary) kind =
let allowedBinaryLength = kind * 8
maxBinaryValue = 2 ^ (allowedBinaryLength - 1) - 1
minBinaryValue = (-1) * 2 ^ (allowedBinaryLength - 1)
truncatedBinary = reverse . take allowedBinaryLength . reverse $ binary
decimal = numsysStringToDecimal truncatedBinary 2
overflow = decimal - maxBinaryValue
in if overflow > 0 then Int (minBinaryValue + overflow - 1) else Int decimal
getDigits :: String -> String
getDigits bozStr = [ toLower c | c <- digits ]
where
digits =
foldr (\l acc -> if l `elem` "'boxz" then acc else l : acc) [] bozStr
toBinaryString :: String -> Int -> String
toBinaryString digits fromNumsys = decimalToBinaryString decimal
where decimal = numsysStringToDecimal digits fromNumsys
numsysStringToDecimal :: String -> Int -> Int
numsysStringToDecimal digits numsys = decimal
where
numsysValidFunction = (`elem` map intToDigit [0 .. (numsys - 1)])
numsysReader = readInt numsys numsysValidFunction digitToInt :: ReadS Int
((decimal, _) : _) = numsysReader digits
decimalToBinaryString :: Int -> String
decimalToBinaryString decimal = showIntAtBase 2 intToDigit decimal ""
-- | Given 'SymbolTable', contextual symbol name and BOZ Constant
-- ('ExpVal' constructed with Boz String), resolve BOZ Constant considering
-- contextual symbol.
--
-- Currently, it only resolves BOZ Constants in context of INTEGER.
resolveBozConstant :: SymbolTable -> String -> ExpVal -> ExpVal
resolveBozConstant symTable assignSymbol boz@(Boz _) =
resolveBozConstant' symTable assignSymbol (parseBozDecomposed boz)
resolveBozConstant _ _ _ = error "Can only resolve ExpVal Boz"
-- Convert BOZ string to integer of specific kind
bozToInt :: Int -> ExpVal -> ExpVal
bozToInt kind boz = resolveBozConstantInContext (parseBozDecomposed boz) kind
-- Convert BOZ string to integer*2
bozToInt2 :: ExpVal -> ExpVal
bozToInt2 = bozToInt 2
-- Convert BOZ string to integer*4
bozToInt4 :: ExpVal -> ExpVal
bozToInt4 = bozToInt 4
-- Convert BOZ string to integer*8
bozToInt8 :: ExpVal -> ExpVal
bozToInt8 = bozToInt 8