packages feed

fortran-vars-0.1.0: src/Language/Fortran/Vars/Operation.hs

module Language.Fortran.Vars.Operation
  ( valueToExpVal'
  , valueToExpVal
  , transformEither
  , transformEitherList
  , binaryTransformEither
  , unaryOp'
  , unaryOp
  , binaryOp'
  , binaryOp
  , intrinsicFunctionCall
  , nonLogicalToLogical
  )
where

import           Prelude                 hiding ( GT
                                                , EQ
                                                , LT
                                                )
import           Data.Char                      ( toUpper
                                                , chr
                                                )
import           Data.Either                    ( either )
import           Text.Read                      ( readMaybe )

import           Language.Fortran.AST           ( BinaryOp(..)
                                                , UnaryOp(..)
                                                , Value(..)
                                                )
import           Language.Fortran.Util.Position ( SrcSpan )


import           Language.Fortran.Vars.BozConstant
                                                ( bozToInt8
                                                , bozToInt
                                                )
import           Language.Fortran.Vars.Errors
                                                ( invalidArg' )
import           Language.Fortran.Vars.Types
                                                ( ExpVal(..) )

import           Data.Bits                      ( (.|.)
                                                , complement
                                                )

-- | Given a 'SrcSpan' and the 'String' string at that span,
-- return either a string describing the error encountered, or
-- the double held by that string.
readReal :: SrcSpan -> String -> Either String Double
readReal s r =
  let r' = transform r
  in  maybe
          (Left $ "Expected a real value at " ++ show s ++ " got '" ++ r' ++ "'"
          )
          Right
        $ readMaybe r'
 where
  uniformPrecision 'D' = 'E'
  uniformPrecision 'Q' = 'E'
  uniformPrecision c   = c
  transform' []             = []
  transform' ['.'         ] = []
  transform' ('.' : y : ys) = case uniformPrecision (toUpper y) of
    'E' -> 'E' : transform' ys
    _   -> '.' : y : transform' ys
  transform' (x : xs) = uniformPrecision (toUpper x) : transform' xs
  transform xs@('.' : _) = transform' $ '0' : xs
  transform xs           = transform' xs


-- | Given a 'String', return either a 'String' describing the
-- issue that prevented the read or the 'Bool' value contained
-- in the string
readLogical :: String -> Either String Bool
readLogical s = case map toUpper s of
  ".TRUE."  -> Right True
  ".FALSE." -> Right False
  _         -> invalidArg' "readLogical" [s]

-- | Given a function that returns an 'Either' and an 'Either' with
-- the 'Right' case as the same type input to the function, return
-- an either by possibly applying the function to the 'Right' value or
-- propagating the 'Left' case
transformEither :: (a -> Either String b) -> Either String a -> Either String b
transformEither = either Left
{-# INLINABLE transformEither #-}

-- | Given a function that takes two arguments of the same type and returns an
-- 'Either' as well as two 'Either's whose 'Right' cases hold the inputs to the
-- function, apply the function if possible. Otherwise propagate the 'Left' cases
binaryTransformEither
  :: (a -> a -> Either String b)
  -> Either String a
  -> Either String a
  -> Either String b
binaryTransformEither _ (Left e)   _          = Left e
binaryTransformEither _ _          (Left  e ) = Left e
binaryTransformEither t (Right v1) (Right v2) = t v1 v2

-- | Given a function that takes a list of arguments of the same type and returns an
-- 'Either' as well as a list of 'Either's whose 'Right' cases hold the inputs to the
-- function, apply the function if possible. Otherwise propagate the 'Left' cases
transformEitherList
  :: ([a] -> Either String b) -> [Either String a] -> Either String b
transformEitherList t el = case eitherListToList el of
  Left  l  -> Left l
  Right rs -> t rs
 where
  eitherListToList :: [Either String a] -> Either String [a]
  eitherListToList []             = Right []
  eitherListToList (Left  l : _ ) = Left l
  eitherListToList (Right r : rs) = case eitherListToList rs of
    Left  l   -> Left l
    Right rs' -> Right (r : rs')

-- | Given a 'SrcSpan' and the 'Value' in that span either
-- return a 'String' describing the issue or the 'ExpVal' held
-- by that 'Value'.
valueToExpVal' :: SrcSpan -> Value a -> Either String ExpVal
valueToExpVal' s val = case val of
  ValInteger i ->
    let i' = readMaybe i :: Maybe Int
    in  case i' of
          Just valInt -> Right $ Int valInt
          Nothing     -> Right $ Boz i
  ValReal      r  -> transformEither (Right . Real) $ readReal s r
  ValString    s' -> Right $ Str s'
  ValLogical   l  -> transformEither (Right . Logical) $ readLogical l
  ValHollerith h  -> Right $ Str h
  _               -> Left ("toExpVal: unsupported value at " ++ show s)

-- | Given a 'SrcSpan' and the 'Value' returnthe 'ExpVal' held
-- by that 'Value' or throw an error.
valueToExpVal :: SrcSpan -> Value a -> ExpVal
valueToExpVal s val = case valueToExpVal' s val of
  Left  err  -> error err
  Right expr -> expr

-- | Given a non-logical 'ExpVal', convert that value to a logical
-- one or return a 'String' describing why this was impossible.
nonLogicalToLogical :: ExpVal -> Either String Bool
nonLogicalToLogical (Int  i) = Right $ i /= 0
nonLogicalToLogical (Real r) = Right $ r /= 0.0
nonLogicalToLogical (Str _) =
  Left "Cannot transform a string value to a logical value"
nonLogicalToLogical (  Logical l) = Right l
nonLogicalToLogical b@(Boz     _) = nonLogicalToLogical $ bozToInt8 b

-- | Given a string representing a function call and a list of ExpVal
-- values holding inputs to the function, evaluate the function call
-- and return the result in a Right, or propagate the Left case if any
-- of the list elements are 'Lefts'.
intrinsicFunctionCall :: String -> [ExpVal] -> Either String ExpVal
intrinsicFunctionCall function es = case function of
  "ior"  -> ior' es
  "max"  -> max' es
  "char" -> char' es
  "not"  -> not' es
  "int"  -> int' es
  "int2" -> int' es
  _      -> invalidArg' ("intrinsicFunctionCall " ++ show function) es

ior' :: [ExpVal] -> Either String ExpVal
ior' [val1, val2] = case (val1, val2) of
  (Int a, Int b) -> Right $ Int $ (.|.) a b
  _              -> invalidArg' "ior" [val1, val2]
ior' vs = invalidArg' "ior" vs

max' :: [ExpVal] -> Either String ExpVal
max' [val1] = case val1 of
  Real a -> Right $ Real a
  Int  a -> Right $ Int a
  _      -> invalidArg' "max" [val1]
max' (v : vs) =
  let maxVs = max' vs
  in  case (v, maxVs) of
        (_      , Left l        ) -> Left l
        (Real r', Right (Int r) ) -> Right $ Real $ max r' (fromIntegral r)
        (Int  r', Right (Real r)) -> Right $ Real $ max (fromIntegral r') r
        (Real r', Right (Real r)) -> Right $ Real $ max r' r
        (Int  r', Right (Int r) ) -> Right $ Int $ max r' r
        _                         -> invalidArg' "max" (v : vs)
max' vs = invalidArg' "max" vs

char' :: [ExpVal] -> Either String ExpVal
char' [Int i] = Right $ Str [chr i]
char' vs      = invalidArg' "char" vs

-- https://docs.oracle.com/cd/E19957-01/805-4939/6j4m0vnc8/index.html
not' :: [ExpVal] -> Either String ExpVal
not' [Int i] = Right $ Int (complement i)
not' vs      = invalidArg' "not" vs

int' :: [ExpVal] -> Either String ExpVal
int' [Int  i] = Right $ Int i
int' [Real r] = Right $ Int (truncate r)
int' v@[boz@(Boz _), Int k] =
  if k `elem` [2, 4, 8] then Right $ bozToInt k boz else invalidArg' "int" v
int' vs = invalidArg' "int" vs

-- | Given a 'UnaryOp' and an 'ExpVal', either return the resulting
-- 'ExpVal' after applying the operation or a 'String' describing
-- why this couldn't be done
unaryOp' :: UnaryOp -> ExpVal -> Either String ExpVal
unaryOp' op v = case (op, v) of
  (Plus , Int a ) -> Right $ Int a
  (Plus , Real a) -> Right $ Real a
  (Minus, Int a ) -> Right $ Int (negate a)
  (Minus, Real a) -> Right $ Real (negate a)
  (Not, a) -> transformEither (Right . Logical . not) $ nonLogicalToLogical a
  _               -> invalidArg' (show op) [v]

-- | Given a 'UnaryOp' and an 'ExpVal', either return the resulting
-- 'ExpVal' after applying the operation or throw an error
unaryOp :: UnaryOp -> ExpVal -> ExpVal
unaryOp op v = case unaryOp' op v of
  Left  err  -> error err
  Right expr -> expr

-- | Given a 'BinaryOp' and two 'ExpVal's, either return the resulting
-- 'ExpVal' after applying the operation or a 'String' describing
-- why this couldn't be done
binaryOp' :: BinaryOp -> ExpVal -> ExpVal -> Either String ExpVal
binaryOp' op val1 val2 = case (op, val1, val2) of
  (Addition, Int a, Int b) -> Right $ Int (a + b)
  (Addition, Real a, Real b) -> Right $ Real (a + b)
  (Addition, Int a, Real b) -> Right $ Real (fromIntegral a + b)
  (Addition, Real a, Int b) -> Right $ Real (a + fromIntegral b)

  (Subtraction, Int a, Int b) -> Right $ Int (a - b)
  (Subtraction, Real a, Real b) -> Right $ Real (a - b)
  (Subtraction, Int a, Real b) -> Right $ Real (fromIntegral a - b)
  (Subtraction, Real a, Int b) -> Right $ Real (a - fromIntegral b)

  (Multiplication, Int a, Int b) -> Right $ Int (a * b)
  (Multiplication, Real a, Real b) -> Right $ Real (a * b)
  (Multiplication, Int a, Real b) -> Right $ Real (fromIntegral a * b)
  (Multiplication, Real a, Int b) -> Right $ Real (a * fromIntegral b)

  (Division, Int a, Int b) -> Right $ Int (div a b)
  (Division, Real a, Real b) -> Right $ Real (a / b)
  (Division, Int a, Real b) -> Right $ Real (fromIntegral a / b)
  (Division, Real a, Int b) -> Right $ Real (a / fromIntegral b)

  (Exponentiation, Int a, Int b) -> Right $ Int (a ^ b)
  (Exponentiation, Real a, Real b) -> Right $ Real (a ** b)
  (Exponentiation, Int a, Real b) -> Right $ Real (fromIntegral a ** b)
  (Exponentiation, Real a, Int b) -> Right $ Real (a ** fromIntegral b)

  (Concatenation, Str a, Str b) -> Right $ Str (a ++ b)

  (LT, Int a, Int b) -> Right $ Logical (a < b)
  (LT, Real a, Real b) -> Right $ Logical (a < b)
  (LT, Int a, Real b) -> Right $ Logical (fromIntegral a < b)
  (LT, Real a, Int b) -> Right $ Logical (a < fromIntegral b)
  (LT, a@(Boz _), b) -> binaryOp' LT (bozToInt8 a) b
  (LT, a, b@(Boz _)) -> binaryOp' LT a (bozToInt8 b)

  (EQ, Int a, Real b) -> Right $ Logical (fromIntegral a == b)
  (EQ, Real a, Int b) -> Right $ Logical (a == fromIntegral b)
  (EQ, a@(Boz _), b) -> binaryOp' EQ (bozToInt8 a) b
  (EQ, a, b@(Boz _)) -> binaryOp' EQ a (bozToInt8 b)
  (EQ, Logical True, Int b) -> Right $ Logical (1 == b)
  (EQ, Logical False, Int b) -> Right $ Logical (0 == b)
  (EQ, Int a, Logical True) -> Right $ Logical (a == 1)
  (EQ, Int a, Logical False) -> Right $ Logical (a == 0)
  (EQ, Logical True, Real b) -> Right $ Logical (1.0 == b)
  (EQ, Logical False, Real b) -> Right $ Logical (0.0 == b)
  (EQ, Real a, Logical True) -> Right $ Logical (a == 1.0)
  (EQ, Real a, Logical False) -> Right $ Logical (a == 0.0)
  (EQ, v1, v2) -> Right $ Logical (v1 == v2)


  (GT, v1, v2) -> binaryOp' LT v2 v1
  (GTE, v1, v2) -> transformEither (unaryOp' Not) $ binaryOp' LT v2 v1
  (LTE, v1, v2) -> transformEither (unaryOp' Not) $ binaryOp' GT v2 v1

  (NE, v1, v2) -> transformEither (unaryOp' Not) $ binaryOp' EQ v1 v2

  (And, v1, v2) ->
    binaryTransformEither (\x -> Right . Logical . (x &&))
                          (nonLogicalToLogical v1)
      $ nonLogicalToLogical v2

  (Or, v1, v2) ->
    binaryTransformEither (\x -> Right . Logical . (x ||))
                          (nonLogicalToLogical v1)
      $ nonLogicalToLogical v2

  (XOr, Logical a, Logical b) -> Right $ Logical (a /= b)
  (Equivalent, Logical a, Logical b) -> Right $ Logical (a == b)
  (NotEquivalent, Logical a, Logical b) -> Right $ Logical (a /= b)
  _ -> invalidArg' (show op) [val1, val2]

-- | Given a 'BinaryOp' and two 'ExpVal's, either return the resulting
-- 'ExpVal' after applying the operation or throw an error
binaryOp :: BinaryOp -> ExpVal -> ExpVal -> ExpVal
binaryOp op val1 val2 = case binaryOp' op val1 val2 of
  Left  err  -> error err
  Right expr -> expr