packages feed

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