module Camfort.Specification.Units.Environment where
import Control.Monad.State.Strict hiding (gets)
import qualified Language.Fortran.AST as F
import qualified Language.Fortran.Analysis as FA
import qualified Language.Fortran.Util.Position as FU
import qualified Camfort.Specification.Units.Parser as P
import Data.Char
import Data.Data
import Data.List
import Data.Matrix
import Data.Ratio
import Data.Binary
import GHC.Generics (Generic)
import qualified Debug.Trace as D
import Text.Printf
type VV = (F.Name, F.Name)
data UnitInfo
= UnitParamPosAbs (String, Int)
| UnitParamPosUse (String, Int, Int)
| UnitParamVarAbs (String, VV)
| UnitParamVarUse (String, VV, Int)
| UnitParamLitAbs Int
| UnitParamLitUse (Int, Int)
| UnitParamEAPAbs VV
| UnitParamEAPUse (VV, Int)
| UnitLiteral Int
| UnitlessLit
| UnitlessVar
| UnitName String
| UnitAlias String
| UnitVar VV
| UnitMul UnitInfo UnitInfo
| UnitPow UnitInfo Double
| UnitRecord [(String, UnitInfo)]
deriving (Eq, Ord, Data, Typeable, Generic)
instance Binary UnitInfo
instance Show UnitInfo where
show u = case u of
UnitParamPosAbs (f, i) -> printf "#<ParamPosAbs %s[%d]>" f i
UnitParamPosUse (f, i, j) -> printf "#<ParamPosUse %s[%d] callId=%d>" f i j
UnitParamVarAbs (f, (v, _)) -> printf "#<ParamVarAbs %s.%s>" f v
UnitParamVarUse (f, (v, _), j) -> printf "#<ParamVarUse %s.%s callId=%d>" f v j
UnitParamLitAbs i -> printf "#<ParamLitAbs litId=%d>" i
UnitParamLitUse (i, j) -> printf "#<ParamLitUse litId=%d callId=%d]>" i j
UnitParamEAPAbs (v, _) -> v
UnitParamEAPUse ((v, _), i) -> printf "#<ParamEAPUse %s callId=%d]>" v i
UnitLiteral i -> printf "#<Literal id=%d>" i
UnitlessLit -> "1"
UnitlessVar -> "1"
UnitName name -> name
UnitAlias name -> name
UnitVar (vName, _) -> printf "#<Var %s>" vName
UnitRecord recs -> "record (" ++ intercalate ", " (map (\ (n, u) -> n ++ " :: " ++ show u) recs) ++ ")"
UnitMul u1 (UnitPow u2 k)
| k < 0 -> maybeParen u1 ++ " / " ++ maybeParen (UnitPow u2 (k))
UnitMul u1 u2 -> maybeParenS u1 ++ " " ++ maybeParenS u2
UnitPow u 1 -> show u
UnitPow u 0 -> "1"
UnitPow u k ->
case doubleToRationalSubset k of
Just r
| e <- showRational r
, e /= "1" -> printf "%s**%s" (maybeParen u) e
| otherwise -> show u
Nothing -> error $
printf "Irrational unit exponent: %s**%f" (maybeParen u) k
where showRational r
| r < 0 = printf "(%s)" (showRational' r)
| otherwise = showRational' r
showRational' r
| denominator r == 1 = show (numerator r)
| otherwise = printf "(%d / %d)" (numerator r) (denominator r)
where
maybeParen x | all isAlphaNum s = s
| otherwise = "(" ++ s ++ ")"
where s = show x
maybeParenS x | all isUnitMulOk s = s
| otherwise = "(" ++ s ++ ")"
where s = show x
isUnitMulOk c = isSpace c || isAlphaNum c || c `elem` "*."
doubleToRationalSubset :: Double -> Maybe Rational
doubleToRationalSubset x | x < 0 =
doubleToRationalSubset (abs x) >>= (\x -> return (x))
doubleToRationalSubset x =
doubleToRational' 0 1 (ceiling x) 1
where
n = 16
doubleToRational' a b c d
| b <= n && d <= n =
let mediant = (fromIntegral (a+c))/(fromIntegral (b+d))
in if x == mediant
then if b + d <= n
then Just $ (a + c) % (b + d)
else Nothing
else if x > mediant
then doubleToRational' (a+c) (b+d) c d
else doubleToRational' a b (a+c) (b+d)
| b > n = Just $ c % d
| otherwise = Just $ a % b
data Constraint
= ConEq UnitInfo UnitInfo
| ConConj [Constraint]
deriving (Eq, Ord, Data, Typeable, Generic)
instance Binary Constraint
type Constraints = [Constraint]
instance Show Constraint where
show (ConEq u1 u2) = show u1 ++ " === " ++ show u2
show (ConConj cs) = intercalate " && " (map show cs)
isVarUnit (UnitVar _) = True
isVarUnit (UnitParamVarUse _) = True
isVarUnit _ = False
isUnresolvedUnit (UnitVar _) = True
isUnresolvedUnit (UnitParamVarUse _) = True
isUnresolvedUnit (UnitParamVarAbs _) = True
isUnresolvedUnit (UnitParamPosUse _) = True
isUnresolvedUnit (UnitParamPosAbs _) = True
isUnresolvedUnit (UnitParamLitUse _) = True
isUnresolvedUnit (UnitParamLitAbs _) = True
isUnresolvedUnit (UnitParamEAPAbs _) = True
isUnresolvedUnit (UnitParamEAPUse _) = True
isUnresolvedUnit (UnitPow u _) = isUnresolvedUnit u
isUnresolvedUnit (UnitMul u1 u2) = isUnresolvedUnit u1 || isUnresolvedUnit u2
isUnresolvedUnit _ = False
isResolvedUnit = not . isUnresolvedUnit
pprintConstr :: Constraint -> String
pprintConstr (ConEq u1 u2)
| isResolvedUnit u1 = "'" ++ pprintUnitInfo u2 ++ "' should have unit '" ++ pprintUnitInfo u1 ++ "'"
| isResolvedUnit u2 = "'" ++ pprintUnitInfo u1 ++ "' should have unit '" ++ pprintUnitInfo u2 ++ "'"
pprintConstr (ConEq u1 u2) = "'" ++ pprintUnitInfo u1 ++ "' should have the same units as '" ++ pprintUnitInfo u2 ++ "'"
pprintConstr (ConConj cs) = intercalate "\n\t and " (map pprintConstr cs)
pprintUnitInfo :: UnitInfo -> String
pprintUnitInfo (UnitVar (_, sName)) = printf "%s" sName
pprintUnitInfo (UnitParamVarUse (_, (_, sName), _)) = printf "%s" sName
pprintUnitInfo (UnitParamPosUse (fname, 0, _)) = printf "result of %s" fname
pprintUnitInfo (UnitParamPosUse (fname, i, _)) = printf "parameter %d to %s" i fname
pprintUnitInfo (UnitParamEAPUse ((v, _), _)) = printf "explicitly annotated polymorphic unit %s" v
pprintUnitInfo (UnitLiteral _) = "literal number"
pprintUnitInfo ui = show ui
conParamEq :: Constraint -> Constraint -> Bool
conParamEq (ConEq lhs1 rhs1) (ConEq lhs2 rhs2) = unitParamEq lhs1 lhs2 || unitParamEq rhs1 rhs2 ||
unitParamEq rhs1 lhs2 || unitParamEq lhs1 rhs2
conParamEq (ConConj cs1) (ConConj cs2) = and $ zipWith conParamEq cs1 cs2
conParamEq _ _ = False
unitParamEq :: UnitInfo -> UnitInfo -> Bool
unitParamEq (UnitParamLitAbs i) (UnitParamLitUse (i', _)) = i == i'
unitParamEq (UnitParamLitUse (i', _)) (UnitParamLitAbs i) = i == i'
unitParamEq (UnitParamVarAbs (f, i)) (UnitParamVarUse (f', i', _)) = (f, i) == (f', i')
unitParamEq (UnitParamVarUse (f', i', _)) (UnitParamVarAbs (f, i)) = (f, i) == (f', i')
unitParamEq (UnitParamPosAbs (f, i)) (UnitParamPosUse (f', i', _)) = (f, i) == (f', i')
unitParamEq (UnitParamPosUse (f', i', _)) (UnitParamPosAbs (f, i)) = (f, i) == (f', i')
unitParamEq (UnitParamEAPAbs v) (UnitParamEAPUse (v', _)) = v == v'
unitParamEq (UnitParamEAPUse (v', _)) (UnitParamEAPAbs v) = v == v'
unitParamEq (UnitMul u1 u2) (UnitMul u1' u2') = unitParamEq u1 u1' && unitParamEq u2 u2' ||
unitParamEq u1 u2' && unitParamEq u2 u1'
unitParamEq (UnitPow u p) (UnitPow u' p') = unitParamEq u u' && p == p'
unitParamEq u1 u2 = u1 == u2
data UnitAnnotation a = UnitAnnotation {
prevAnnotation :: a,
unitSpec :: Maybe P.UnitStatement,
unitConstraint :: Maybe Constraint,
unitInfo :: Maybe UnitInfo,
unitBlock :: Maybe (F.Block (FA.Analysis (UnitAnnotation a))),
unitPU :: Maybe (F.ProgramUnit (FA.Analysis (UnitAnnotation a)))
} deriving (Data, Typeable, Show)
dbgUnitAnnotation (UnitAnnotation _ s c i b p) =
"{ unitSpec = " ++ show s ++ ", unitConstraint = " ++ show c ++ ", unitInfo = " ++ show i ++ ", unitBlock = " ++
(case b of
Nothing -> "Nothing"
Just (F.BlStatement _ span _ (F.StDeclaration {})) -> "Just {decl}@" ++ show span
Just (F.BlStatement _ span _ _) -> "Just {stmt}@" ++ show span
Just _ -> "Just ...")
++ ", unitPU = " ++
(case p of
Nothing -> "Nothing"
Just (F.PUFunction _ span _ _ _ _ _ _ _) -> "Just {func}@" ++ show span
Just (F.PUSubroutine _ span _ _ _ _ _) -> "Just {subr}@" ++ show span
Just _ -> "Just ...")
++ "}"
mkUnitAnnotation :: a -> UnitAnnotation a
mkUnitAnnotation a = UnitAnnotation a Nothing Nothing Nothing Nothing Nothing
toUnitInfo :: P.UnitOfMeasure -> UnitInfo
toUnitInfo (P.UnitProduct u1 u2) = UnitMul (toUnitInfo u1) (toUnitInfo u2)
toUnitInfo (P.UnitQuotient u1 u2) = UnitMul (toUnitInfo u1) (UnitPow (toUnitInfo u2) (1))
toUnitInfo (P.UnitExponentiation u1 p) = UnitPow (toUnitInfo u1) (toDouble p)
where
toDouble :: P.UnitPower -> Double
toDouble (P.UnitPowerInteger i) = fromInteger i
toDouble (P.UnitPowerRational x y) = fromRational (x % y)
toUnitInfo (P.UnitBasic str) = UnitName str
toUnitInfo (P.Unitless) = UnitlessLit
toUnitInfo (P.UnitRecord us) = UnitRecord (map (fmap toUnitInfo) us)