{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE OverloadedStrings #-} module Language.Eiffel.Util where import Control.Applicative hiding (getConst) import Control.Monad import Control.Lens hiding (from, lens) import Data.Maybe import qualified Data.HashMap.Strict as Map import qualified Data.Text as Text import Data.Text (Text) import Language.Eiffel.Syntax -- Class level utilities -- | A 'Feature' can provide its name, arguments, contract, etc. class Feature a expr | a -> expr where -- | The name of the feature. featureName :: a -> Text -- | Argument declarations. featureArgs :: a -> [Decl] -- | Result type. featureResult :: a -> Typ -- | Precondition. featurePre :: a -> [Clause expr] -- | Postconditions. featurePost :: a -> [Clause expr] -- | Whether the feature is frozen (can't be redefined). featureIsFrozen :: a -> Bool -- | Transform the feature given a renaming clause. featureRename :: a -> RenameClause -> a -- | An existential type to aggregate -- all features (routines, attributes, constants) together. data FeatureEx expr = -- | Wrap the 'Feature' in the existential type. forall a. Feature a expr => FeatureEx a instance Feature (FeatureEx expr) expr where featureName (FeatureEx f) = featureName f featureArgs (FeatureEx f) = featureArgs f featureResult (FeatureEx f) = featureResult f featurePre (FeatureEx f) = featurePre f featurePost (FeatureEx f) = featurePost f featureIsFrozen (FeatureEx f) = featureIsFrozen f featureRename (FeatureEx f) = FeatureEx . featureRename f -- | Wrap up SomeFeature wrapSomeFeature :: (forall f . Feature f expr => f -> b) -> SomeFeature body expr -> b wrapSomeFeature f (SomeRoutine r) = f r wrapSomeFeature f (SomeAttr a) = f a wrapSomeFeature f (SomeConst c) = f c instance Feature (SomeFeature body expr) expr where featureName = wrapSomeFeature featureName featureArgs = wrapSomeFeature featureArgs featureResult = wrapSomeFeature featureResult featurePre = wrapSomeFeature featurePre featurePost = wrapSomeFeature featurePost featureIsFrozen = wrapSomeFeature featureIsFrozen featureRename (SomeRoutine rout) r = SomeRoutine (featureRename rout r) featureRename (SomeAttr attr) r = SomeAttr (featureRename attr r) featureRename (SomeConst c) r = SomeConst (featureRename c r) instance Feature (AbsRoutine body expr) expr where featureName = routineName featureArgs = routineArgs featureResult = routineResult featurePre = contractClauses . routineReq featurePost = contractClauses . routineEns featureIsFrozen = routineFroz featureRename rout r@(Rename orig new alias) | routineName rout == orig = rout { routineName = new , routineAlias = alias , routineArgs = newArgs } | otherwise = rout {routineArgs = newArgs} where newArgs = map (renameDecl r) (routineArgs rout) instance Feature (Attribute expr) expr where featureName = declName . attrDecl featureArgs = const [] featureResult = declType . attrDecl featurePre = contractClauses . attrReq featurePost = contractClauses . attrEns featureIsFrozen = attrFroz featureRename attr r = attr {attrDecl = renameDecl r (attrDecl attr)} instance Feature (Constant expr) expr where featureName = declName . constDecl featureArgs = const [] featureResult = declType . constDecl featurePre _ = [] featurePost _ = [] featureIsFrozen = constFroz featureRename constant r = constant {constDecl = renameDecl r (constDecl constant)} -- | A way to extract each type of feature from a class. class Feature a expr => ClassFeature a body expr | a -> expr, a -> body where -- | A list of all this class' features of the given type. allFeatures :: AbsClas body expr -> [a] -- | Find this kind of feature in a class findFeature :: AbsClas body expr -> Text -> Maybe a instance ClassFeature (Constant expr) body expr where findFeature = findFeature' toConstMb allFeatures = allConstants instance ClassFeature (AbsRoutine body expr) body expr where findFeature = findFeature' toRoutineMb allFeatures = allRoutines instance ClassFeature (Attribute expr) body expr where findFeature = findFeature' toAttrMb allFeatures = allAttributes instance ClassFeature (FeatureEx expr) body expr where allFeatures clas = map FeatureEx (allAttributes clas) ++ map FeatureEx (allRoutines clas) ++ map FeatureEx (allConstants clas) findFeature = findFeature' (Just . FeatureEx) findFeature' :: (SomeFeature body expr -> Maybe a) -> AbsClas body expr -> Text -> Maybe a findFeature' from cls name = join $ from <$> findSomeFeature cls name -- | Convert a constant into an attribute. constToAttr :: Constant exp -> Attribute Expr constToAttr (Constant froz d _) = Attribute froz d Nothing [] (Contract False []) (Contract False []) -- * Extracting data from a class. -- | Fetch attributes from all feature clauses. allAttributes = allHelper fmAttrs -- | Fetch routines from all feature clauses. allRoutines = allHelper fmRoutines -- | Fetch contants from all feature clauses. allConstants = allHelper fmConsts -- Help for above 'all' functions allHelper lens = map (view exportFeat) . Map.elems . view lens . featureMap -- | Fetch creation routines from all feature clauses. allCreates = concatMap createNames . creates -- | Fetch attribute declarations from all feature clauses. allAttributeDecls = map attrDecl . allAttributes -- | Fetch constant declarations from all feature clauses. allConstantDecls = map constDecl . allConstants -- | All inheritance clauses. allInherited = concatMap inheritClauses . inherit -- | All inherited classes, as types. allInheritedTypes = map inheritClass . allInherited -- | Determine if a name is in the creation clause of a class. isCreateName n c = n `elem` allCreates c -- * 'SomeFeature' predicates, extractors. toRoutineMb (SomeRoutine r) = Just r toRoutineMb _ = Nothing toAttrMb (SomeAttr r) = Just r toAttrMb _ = Nothing toConstMb (SomeConst r) = Just r toConstMb _ = Nothing -- isRoutine (SomeRoutine _) = True -- isRoutine _ = False -- isAttr (SomeAttr _) = True -- isAttr _ = False -- isConst (SomeConst _) = True -- isConst _ = False -- getRoutine (SomeRoutine r) = r -- getAttr (SomeAttr a) = a -- getConst (SomeConst c) = c -- onlyRoutine f (SomeRoutine a) = SomeRoutine (f a) -- onlyRoutine _f other = other -- onlyAttr f (SomeAttr a) = SomeAttr (f a) -- onlyAttr _f other = other -- onlyConst f (SomeConst a) = SomeConst (f a) -- onlyConst _f other = other -- onlyRoutineM f (SomeRoutine a) = SomeRoutine <$> f a -- onlyRoutineM f other = pure other -- onlyAttrM :: Applicative m -- => (Attribute exp -> m (Attribute exp)) -- -> SomeFeature body exp -- -> m (SomeFeature body exp) -- onlyAttrM f (SomeAttr a) = SomeAttr <$> f a -- onlyAttrM f other = pure other -- onlyConstM f (SomeConst a) = SomeConst (f a) -- onlyConstM f other = other -- * Class modification -- ** Setting members of a class. -- | Set the feature clause of a class. updFeatureMap :: AbsClas body exp -> FeatureMap body exp -> AbsClas body exp updFeatureMap c featMap = c {featureMap = featMap} -- | Update a routine body. updFeatBody :: RoutineBody a -> PosAbsStmt b -> RoutineBody b updFeatBody impl body = impl {routineBody = body} -- ** Mapping features of a class -- | These functions will update a class or feature clause with a transformation -- function. -- | Map a transformation function over the routines in a class, replacing the -- old routines with the transformed versions within a feature clause. -- mapRoutines f = Map.map (over exportFeat (onlyRoutine f)) -- | Monadic version of 'mapRoutines'. mapRoutinesM :: (Applicative m, Monad m) => (AbsRoutine body exp -> m (AbsRoutine body exp)) -> FeatureMap body exp -> m (FeatureMap body exp) mapRoutinesM f = mapMOf (fmRoutines.traverse.exportFeat) f -- | Map a transformation function over the attributes in a class, -- replacing the old attributes with the transformed versions within a feature clause. mapAttributes :: (Attribute exp -> Attribute exp) -> FeatureMap body exp -> FeatureMap body exp mapAttributes f = over (fmAttrs.traverse.exportFeat) f -- | Monadic version of 'mapAttributes'. mapAttributesM :: (Monad m, Applicative m) => (Attribute exp -> m (Attribute exp)) -> FeatureMap body exp -> m (FeatureMap body exp) mapAttributesM f = mapMOf (fmAttrs.traverse.exportFeat) f -- | Map a transformation function over the constants in a class, replacing the -- old constants with the transformed versions within a feature clause. -- mapConstants f = Map.map (over exportFeat (onlyConst f)) -- | Map a transformation function over the contracts in a class, replacing the -- old contracts with the transformed versions within a feature clause. mapContract clauseF cs = cs { contractClauses = map clauseF (contractClauses cs)} -- | Map a transformation function over all expressions in a class. -- A transformation for features, constants, and attributes must be given -- as if the type of expressions changes (ie, with a typecheck) then -- all expressions types must change together. mapExprs :: (AbsRoutine body exp -> AbsRoutine body' exp') -> (Constant exp -> Constant exp') -> (Clause exp -> Clause exp') -> FeatureMap body exp -> FeatureMap body' exp' mapExprs routF constF clauseF fm = FeatureMap (mapUpd routF fmRoutines) (mapUpd updAttr fmAttrs) (mapUpd constF fmConsts) where updAttr a = a { attrEns = mapContract clauseF (attrEns a) , attrReq = mapContract clauseF (attrReq a) } mapUpd f lens = Map.map (over exportFeat f) (view lens fm) -- | Map a transformation function over the attributes in a class, replacing the -- old attributes with the transformed versions within a class. classMapAttributes f c = c {featureMap = mapAttributes f (featureMap c)} -- | Monadic version of 'classMapAttributes'. classMapAttributesM :: (Applicative m, Monad m) => (Attribute exp -> m (Attribute exp)) -> AbsClas body exp -> m (AbsClas body exp) classMapAttributesM f c = do fm <- mapAttributesM f (featureMap c) return (c {featureMap = fm}) -- | Map a transformation function over the routines in a class, replacing the -- old routines with the transformed versions within a class. classMapRoutines :: (AbsRoutine body exp -> AbsRoutine body exp) -> AbsClas body exp -> AbsClas body exp classMapRoutines f c = c {featureMap = over (fmRoutines.traverse.exportFeat) f (featureMap c)} -- | Monadic version of 'classMapRoutines'. classMapRoutinesM :: (Applicative m, Monad m) => (AbsRoutine body exp -> m (AbsRoutine body exp)) -> AbsClas body exp -> m (AbsClas body exp) classMapRoutinesM f c = do fm <- mapRoutinesM f (featureMap c) return (c {featureMap = fm}) -- | Map a transformation function over the constants in a class, replacing the -- old constants with the transformed versions within a class. classMapConstants f c = c {featureMap = over (fmConsts.traverse.exportFeat) f (featureMap c)} -- | Map a transformation function over all expressions in a class. -- A transformation for features, constants, and attributes must be given -- as if the type of expressions changes (ie, with a typecheck) then -- all expressions types must change together. This is performed on every -- feature clause in a class. classMapExprs :: (AbsRoutine body exp -> AbsRoutine body' exp') -> (Clause exp -> Clause exp') -> (Constant exp -> Constant exp') -> AbsClas body exp -> AbsClas body' exp' classMapExprs featrF clauseF constF c = c { featureMap = mapExprs featrF constF clauseF (featureMap c) , invnts = map clauseF (invnts c) } -- * Interface construction -- | Strip the body from a routine. makeRoutineIs :: SomeFeature body Expr -> SomeFeature EmptyBody Expr makeRoutineIs (SomeRoutine r) = SomeRoutine (makeRoutineI r) makeRoutineIs (SomeAttr a) = SomeAttr a makeRoutineIs (SomeConst c) = SomeConst c -- | Strip the contracts from an attribute. makeAttributeI :: Attribute exp -> Attribute Expr makeAttributeI (Attribute froz decl assgn notes _ _) = Attribute froz decl assgn notes (Contract False []) (Contract False []) -- | Strip the bodies from all features. clasInterface :: AbsClas body Expr -> ClasInterface clasInterface c = c { featureMap = over (fmRoutines.traverse.exportFeat) (makeRoutineI) (featureMap c) } -- | Strip the bodies and rescue clause from a routine. makeRoutineI :: AbsRoutine body Expr -> RoutineI makeRoutineI f = f { routineImpl = EmptyBody , routineRescue = Nothing} -- * Map construction -- | Turn a list of classes into a map indexed by the class names. clasMap :: [AbsClas body exp] -> Map ClassName (AbsClas body exp) clasMap = Map.fromList . map (\ c -> (className c, c)) -- | Extract a map of attribute names to types given a class. attrMap :: AbsClas body exp -> Map Text Typ attrMap = declsToMap . map attrDecl . allAttributes -- * Search -- | Find a routine in a class. findRoutine :: Clas -> Text -> Maybe Routine findRoutine = findFeature -- | Find an operator (symbol sequence) in a class. findOperator :: AbsClas body Expr -> Text -> Int -> Maybe (AbsRoutine body Expr) findOperator c opName numArgs = let fs = allRoutines c ffs = filter (\ rout -> routineAlias rout == Just opName && length (routineArgs rout) == numArgs) fs in listToMaybe ffs -- -- | Find a 'ClassFeature'. -- findFeature :: ClassFeature a body expr => -- AbsClas body expr -> Text -> Maybe a -- findFeature clasInt name = -- let fs = filter (\f -> Text.map toLower (featureName f) == Text.map toLower name) -- (allFeatures clasInt) -- in listToMaybe fs -- | Find the sum-type for all features. findSomeFeature :: AbsClas body expr -> Text -> Maybe (SomeFeature body expr) findSomeFeature cls name = lkup fmRoutines SomeRoutine <|> lkup fmAttrs SomeAttr <|> lkup fmConsts SomeConst where lkup lens cast = cast <$> view exportFeat <$> Map.lookup nameLow (view lens featMap) featMap = featureMap cls nameLow = Text.toLower name -- | Find an existential 'FeatureEx'. findFeatureEx :: AbsClas body expr -> Text -> Maybe (FeatureEx expr) findFeatureEx = findFeature -- | Find a routine by name. findRoutineInt :: ClasInterface -> Text -> Maybe RoutineI findRoutineInt = findFeature -- | Find an attribute in a class by name. findAttrInt :: AbsClas body expr -> Text -> Maybe (Attribute expr) findAttrInt = findFeature -- | Find a constant by name in a class. findConstantInt :: AbsClas body Expr -> Text -> Maybe (Constant Expr) findConstantInt = findFeature -- | Given a class and a routine, given a unique name. fullName :: AbsClas body exp -> RoutineI -> Text fullName c f = fullNameStr (className c) (routineName f) -- | Given to string construct a unique combination. fullNameStr :: Text -> Text -> Text fullNameStr cName fName = Text.concat ["__", cName, "_", fName] -- | Given a class, create a list of generic classes for the formal generic -- parameters of the class. genericStubs :: AbsClas body exp -> [AbsClas body' exp'] genericStubs = map makeGenericStub . generics -- | Given a generic, construct a class for the generic. makeGenericStub :: Generic -> AbsClas body exp makeGenericStub (Generic g constrs _) = AbsClas { deferredClass = False , frozenClass = False , expandedClass = False , classNote = [] , className = g , currProc = Dot , procGeneric = [] , obsoleteClass = False , procExpr = [] , generics = [] , inherit = [Inheritance False $ map simpleInherit constrs] , creates = [] , converts = [] , featureMap = FeatureMap Map.empty Map.empty Map.empty , invnts = [] } where simpleInherit t = InheritClause t [] [] [] [] [] -- * Inheritance utilities -- | Rename a declaration. renameDecl :: RenameClause -> Decl -> Decl renameDecl r@(Rename orig new _) (Decl n t) | n == orig = Decl new t' | otherwise = Decl n t' where t' = renameType r t -- | Rename a type, in the case of a like-type. renameType r (ClassType n ts) = ClassType n (map (renameType r) ts) renameType (Rename orig new _) (Like i) | i == orig = Like new | otherwise = Like i renameType r t = error $ "renameType: rename " ++ show r ++ " in type: " ++ show t -- | Rename everything in a class. renameAll :: [RenameClause] -> AbsClas body exp -> AbsClas body exp renameAll renames cls = renamed where renamed = foldr renameClass cls renames renameKey (Rename old new _aliasMb) k | k == Text.toLower old = new | otherwise = k renameKeys r c = c { featureMap = fmMapKeys (renameKey r) (featureMap c)} renameClass r = renameKeys r . classMapConstants (flip featureRename r) . classMapAttributes (flip featureRename r) . classMapRoutines (flip featureRename r) -- | Undefine a single feature in a class. undefineName :: Text -> AbsClas body exp -> AbsClas body exp undefineName name cls = cls { featureMap = fmKeyFilter (/= name) (featureMap cls)} -- | Undefine every specified name for a class. undefineAll :: InheritClause -> AbsClas body exp -> AbsClas body exp undefineAll inh cls = foldr undefineName cls (undefine inh) -- | Specifies whether a class can be merged with another. mergeableClass :: AbsClas body exp -> Bool mergeableClass _clas = True -- null (generics clas) -- && null (inherit clas) -- | Merge two classes, combining invariants and feature clauses. mergeClass :: AbsClas body exp -> AbsClas body exp -> AbsClas body exp mergeClass class1 class2 | mergeableClass class1 && mergeableClass class2 = class1 { invnts = invnts class1 ++ invnts class2 , featureMap = featureMap class1 `fmUnion` featureMap class2 } | otherwise = error $ "mergeClasses: classes not mergeable " ++ show (className class1, className class2) -- | Merge a list of classes. mergeClasses :: [AbsClas body exp] -> AbsClas body exp mergeClasses = foldr1 mergeClass -- * Feature Map functions fmMapKeys :: (Text -> Text) -> FeatureMap body exp -> FeatureMap body exp fmMapKeys f = fmKeyMap fmRoutines . fmKeyMap fmAttrs . fmKeyMap fmConsts where fmKeyMap setter = over setter mapKeys mapKeys :: Map Text v -> Map Text v mapKeys = Map.fromList . map (\(k,v) -> (f k, v)) . Map.toList fmKeyFilter :: (Text -> Bool) -> FeatureMap body exp -> FeatureMap body exp fmKeyFilter p = fmFilt fmRoutines . fmFilt fmAttrs . fmFilt fmConsts where fmFilt setter = over setter filt filt = Map.filterWithKey (\ k _v -> p k) fmUnion :: FeatureMap body exp -> FeatureMap body exp -> FeatureMap body exp fmUnion fm1 fm2 = FeatureMap (Map.union (view fmRoutines fm1) (view fmRoutines fm2)) (Map.union (view fmAttrs fm1) (view fmAttrs fm2)) (Map.union (view fmConsts fm1) (view fmConsts fm2)) fmEmpty = FeatureMap Map.empty Map.empty Map.empty fmUnions = foldr fmUnion fmEmpty -- * Routine level utilities -- | Construct a map from a routine's arguments. argMap :: RoutineWithBody a -> Map Text Typ argMap = declsToMap . routineArgs -- | Construct a map from a routine's declarations. localMap :: RoutineWithBody a -> Map Text Typ localMap = declsToMap . routineDecls -- | Give the declarations of a routine's locals. routineDecls :: AbsRoutine (RoutineBody exp1) exp -> [Decl] routineDecls r = case routineImpl r of RoutineDefer -> [] RoutineExternal _ _ -> [] body -> routineLocal body -- Operator utilities -- | Operator aliases for user-level operators, ie, not including -- =, /=, ~, and /~ opAlias :: BinOp -> Text opAlias Add = "+" opAlias Sub = "-" opAlias Mul = "*" opAlias Div = "/" opAlias Quot = "//" opAlias Rem = "\\" opAlias Pow = "^" opAlias And = "and" opAlias AndThen = "and then" opAlias Or = "or" opAlias OrElse = "or else" opAlias Xor = "xor" opAlias Implies = "implies" opAlias (SymbolOp o) = o opAlias (RelOp o _) = rel o where rel Lte = "<=" rel Lt = "<" rel Gt = ">" rel Gte = ">=" rel relOp = error $ "opAlias: non user-level operator " ++ show relOp -- | Test if the binary operator is an equality operator. equalityOp :: BinOp -> Bool equalityOp (RelOp Eq _) = True equalityOp (RelOp Neq _) = True equalityOp (RelOp TildeEq _) = True equalityOp (RelOp TildeNeq _) = True equalityOp _ = False -- | Unary operator aliases for everything except `old'. unOpAlias Not = "not" unOpAlias Neg = "-" unOpAlias Old = "unOpAlias: `old' is not a user-operator." -- * Type utilities -- | Convert a class into its type. classToType :: AbsClas body exp -> Typ classToType clas = ClassType (className clas) (map genType (generics clas)) where genType g = ClassType (genericName g) [] -- | Whether a type is basic (where basic meaning its an integer, natural, real or boolean). isBasic :: Typ -> Bool isBasic t = any ($ t) [isBooleanType, isIntegerType, isNaturalType, isRealType, isCharType] -- | A list of the number of integer bits (8, 16, ...) intBits :: [Integer] intBits = [8, 16, 32, 64] -- | The bounds on the range of values a integer or natural type can take. typeBounds :: Typ -> (Integer, Integer) typeBounds (ClassType n []) = fromJust $ lookup n wholeMap where intMap = zip integerTypeNames (map (\bits -> let half = bits `quot` 2 in (- 2^half, 2^half - 1)) intBits) natMap = zip naturalTypeNames (map (\bits -> (0, 2^bits - 1)) intBits) wholeMap = intMap ++ natMap typeBounds t = error $ "typeBounds: won't work on " ++ show t -- | Boolean type test. isBooleanType :: Typ -> Bool isBooleanType = (== "BOOLEAN") . classNameType -- | Integer type test. isIntegerType :: Typ -> Bool isIntegerType = isInTypeNames integerTypeNames -- | Natural number type test. isNaturalType :: Typ -> Bool isNaturalType = isInTypeNames naturalTypeNames -- | Real number type test. isRealType :: Typ -> Bool isRealType = isInTypeNames realTypeNames -- | Character type test. isCharType :: Typ -> Bool isCharType = isInTypeNames charTypeNames isInTypeNames names (ClassType name _) = name `elem` names isInTypeNames _ _ = False -- | List of integer type names (ie, INTEGER_32). integerTypeNames :: [Text] integerTypeNames = map ((Text.append "INTEGER_") . Text.pack . show) intBits -- | List of integer type names (ie, NATURAL_32). naturalTypeNames :: [Text] naturalTypeNames = map ((Text.append "NATURAL_") . Text.pack . show) intBits -- | List of integer type names (ie, REAL_64). realTypeNames :: [Text] realTypeNames = ["REAL_32", "REAL_64"] -- | List of integer type names (ie, CHARACTER_8). charTypeNames :: [Text] charTypeNames = ["CHARACTER_8", "CHARACTER_32"] -- | Given a type give the name of the class as a string. classNameType :: Typ -> Text classNameType (ClassType cn _) = cn classNameType (Sep _ _ cn) = cn classNameType t = error $ "Non-class type " ++ show t -- | The default integer type. intType :: Typ intType = namedType "INTEGER_32" -- | The default boolean type. boolType :: Typ boolType = namedType "BOOLEAN" -- | The default real number type. realType :: Typ realType = namedType "REAL_64" -- | The default character type. charType :: Typ charType = namedType "CHARACTER_8" -- | The default string type. stringType :: Typ stringType = namedType "STRING_8" -- | The top type, ANY. anyType :: Typ anyType = namedType "ANY" -- | Construct a simple type from a classname. namedType :: ClassName -> Typ namedType name = ClassType name [] -- * Declaration -- | Insert a declaration into a string-type map. insertDecl :: Decl -> Map Text Typ -> Map Text Typ insertDecl (Decl s t) = Map.insert s t -- | Turn a list of declarations into a string-type map. declsToMap :: [Decl] -> Map Text Typ declsToMap = foldr insertDecl Map.empty -- * SCOOP utilities -- | Given a processor declaration, extract the processor. newVar :: ProcDecl -> Proc newVar (SubTop p) = p newVar (CreateLessThan p _) = p