sv2v/src/Convert/Package.hs

848 lines
36 KiB
Haskell
Raw Normal View History

{-# LANGUAGE TupleSections #-}
2019-04-24 02:22:03 +02:00
{- sv2v
- Author: Zachary Snow <zach@zachjs.com>
-
- Conversion for packages and global declarations
-
- This conversion first makes a best-effort pass at resolving any simple
- declaration ordering issues in the input. Many conversions require that
- declarations precede their first usage.
-
- The main phase elaborates packages and resolves imported identifiers. An
- identifier (perhaps implicitly) referring to `P::X` is rewritten to `P_X`.
- This conversion assumes such renaming will not cause conflicts. The full
- semantics of imports and exports are followed.
-
- Finally, because Verilog doesn't allow declarations to exist outside of
- modules, declarations within packages and in the global scope are injected
- into modules and interfaces as needed.
2019-04-24 02:22:03 +02:00
-}
module Convert.Package
( convert
, inject
, prefixItems
) where
2019-04-24 02:22:03 +02:00
2020-08-12 01:14:18 +02:00
import Control.Monad.State.Strict
import Control.Monad.Writer.Strict
import Data.List (insert, intercalate, isPrefixOf)
import Data.Maybe (mapMaybe)
2019-04-24 02:22:03 +02:00
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Convert.ResolveBindings (resolveBindings)
import Convert.Scoper
2019-04-24 02:22:03 +02:00
import Convert.Traverse
import Language.SystemVerilog.AST
type Packages = Map.Map Identifier Package
type Classes = Map.Map Identifier Class
type Package = (IdentStateMap, [PackageItem])
type Class = ([Decl], [PackageItem])
2019-04-24 02:22:03 +02:00
type Idents = Set.Set Identifier
type PIs = Map.Map Identifier PackageItem
2019-04-24 02:22:03 +02:00
convert :: [AST] -> [AST]
convert files =
map (traverseDescriptions $ convertDescription pis) files'
2019-04-24 02:22:03 +02:00
where
(files', packages') = convertPackages files
pis = Map.fromList $
2021-01-27 22:41:39 +01:00
concatMap (concatMap (toPackageItems . makeLocal) . snd) $
filter (not . Map.null . fst) $
Map.elems packages'
toPackageItems :: PackageItem -> [(Identifier, PackageItem)]
toPackageItems item = map (, item) (piNames item)
-- convert a parameter to a localparam
makeLocal :: PackageItem -> PackageItem
makeLocal (Decl (Param _ t x e)) = Decl $ Param Localparam t x e
makeLocal (Decl (ParamType _ x t)) = Decl $ ParamType Localparam x t
makeLocal other = other
-- utility for inserting package items into a set of module items as needed
inject :: [PackageItem] -> [ModuleItem] -> [ModuleItem]
inject packageItems items =
addItems localPIs Set.empty (map addUsedPIs items)
where
localPIs = Map.fromList $ concatMap toPIElem packageItems
toPIElem :: PackageItem -> [(Identifier, PackageItem)]
toPIElem item = map (, item) (piNames item)
-- utility for appling a prefix to all of the top level items in a module
prefixItems :: Identifier -> [ModuleItem] -> [ModuleItem]
prefixItems prefix items =
snd $ evalState (processItems "" prefix items) initialState
where initialState = PK [] Map.empty Map.empty Set.empty
-- collect packages and global package items
collectPackageM :: Description -> Writer (Packages, Classes, [PackageItem]) ()
collectPackageM (PackageItem item) =
when (not $ null $ piNames item) $
tell (Map.empty, Map.empty, [item])
collectPackageM (Package _ name items) =
tell (Map.singleton name (Map.empty, items), Map.empty, [])
collectPackageM (Class _ name decls items) =
tell (Map.empty, Map.singleton name (decls, map unpackClassItem items), [])
where
unpackClassItem :: ClassItem -> PackageItem
unpackClassItem item@(_, Task{}) = checkTF item
unpackClassItem item@(_, Function{}) = checkTF item
unpackClassItem item = checkNonTF item
checkTF :: ClassItem -> PackageItem
checkTF (QStatic, item) = item
checkTF (_, item) =
error $ "unsupported declaration of non-static method " ++ itemName
where [itemName] = piNames item
checkNonTF :: ClassItem -> PackageItem
checkNonTF (QNone, item) = item
checkNonTF (qualifier, _) =
error $ "unexpected qualifier " ++ show qualifier
++ " on a class item which is not a task or a function"
collectPackageM _ = return ()
-- elaborate all packages and their usages
convertPackages :: [AST] -> ([AST], Packages)
convertPackages files =
(files', packages')
where
(files', PK [] packages' _ _) = runState op initialState
initialState = PK [] packages classes conflicts
op = mapM (traverseDescriptionsM traverseDescriptionM) files
packages = Map.insert "" (Map.empty, globalItems) realPackages
(realPackages, classes, globalItems) =
execWriter $ mapM (collectDescriptionsM collectPackageM) files
prefixes = Set.union (Map.keysSet classes) (Map.keysSet realPackages)
conflicts =
if Set.null prefixes
then Set.empty
else execWriter $ mapM (collectIdentConflicts prefixes) files
-- write down identifiers that might conflict with the generated names for
-- injected package items
collectIdentConflicts :: Idents -> AST -> Writer Idents ()
collectIdentConflicts prefixes =
mapM_ $ collectModuleItemsM collectModuleItem
where
collectModuleItem =
evalScoperT . scoper >=>
collectify traverseIdentsM ident
scoper = scopeModuleItem collectDecl return return return
collectDecl decl = do
case decl of
Variable _ _ x _ _ -> lift $ ident x
Net _ _ _ _ x _ _ -> lift $ ident x
Param _ _ x _ -> lift $ ident x
ParamType _ x _ -> lift $ ident x
CommentDecl{} -> return ()
return decl
ident = collectIdent prefixes
-- write down identifiers that have a package name as a prefix
collectIdent :: Idents -> Identifier -> Writer Idents ()
collectIdent prefixes ident =
case Set.lookupLE ident prefixes of
Just prefix -> when (prefix `isPrefixOf` ident) found
where found = tell $ Set.singleton ident
Nothing -> return ()
data PK = PK
{ pkStack :: [Identifier]
, pkPackages :: Packages
, pkClasses :: Classes
, pkConflicts :: Idents
}
type PackagesState = State PK
traverseDescriptionM :: Description -> PackagesState Description
traverseDescriptionM (PackageItem item) = do
return $ PackageItem $ case piNames item of
[] -> item
idents -> Decl $ CommentDecl $ "removed " ++ show idents
traverseDescriptionM (Package _ name _) =
return $ PackageItem $ Decl $ CommentDecl $ "removed package " ++ show name
traverseDescriptionM (Class _ name _ _) =
return $ PackageItem $ Decl $ CommentDecl $ "removed class " ++ show name
traverseDescriptionM (Part attrs extern kw liftetime name ports items) = do
(_, items') <- processItems name "" items
return $ Part attrs extern kw liftetime name ports items'
data IdentState
= Available [Identifier]
| Imported Identifier
| Declared
deriving Eq
isImported :: IdentState -> Bool
isImported Imported{} = True
isImported _ = False
isDeclared :: IdentState -> Bool
isDeclared Declared{} = True
isDeclared _ = False
type IdentStateMap = Map.Map Identifier IdentState
type Scope = ScoperT IdentState PackagesState
-- produce the partial mapping for a particular export and ensure its validity
resolveExport
:: IdentStateMap -> Identifier -> Identifier -> PackagesState IdentStateMap
resolveExport mapping "" "" =
return $ Map.filter isImported mapping
resolveExport mapping pkg "" =
fmap (Map.mapMaybeWithKey checkExport . fst) (findPackage pkg)
2019-04-24 02:22:03 +02:00
where
checkExport :: Identifier -> IdentState -> Maybe IdentState
checkExport ident exportedIdentState =
if localIdentState == expectedIdentState
then localIdentState
else Nothing
where
localIdentState = Map.lookup ident mapping
expectedIdentState = Just $ Imported $
toRootPackage pkg exportedIdentState
resolveExport mapping pkg ident =
case Map.lookup ident mapping of
Just (Imported importedPkg) -> do
exportedPkg <- resolveRootPackage pkg ident
if importedPkg == exportedPkg
then return $ Map.singleton ident $ Imported importedPkg
else error $ "export of " ++ pkg ++ "::" ++ ident
++ " differs from import of " ++ importedPkg
++ "::" ++ ident
_ -> error $ "export of " ++ pkg ++ "::" ++ ident
++ ", but " ++ ident ++ " was never imported"
-- lookup the state of the identifier only within the current scope
lookupLocalIdentState :: Identifier -> Scope (Maybe IdentState)
lookupLocalIdentState =
fmap (fmap thd3) . lookupLocalIdentM
where thd3 (_, _, c) = c
-- make a particular identifier within a package available for import
wildcardImport :: Identifier -> Identifier -> Scope ()
wildcardImport pkg ident = do
rootPkg <- lift $ resolveRootPackage pkg ident
maybeIdentState <- lookupLocalIdentState ident
insertElem ident $
case maybeIdentState of
Nothing -> Available [rootPkg]
Just Declared -> Declared
Just (Imported existingRootPkg) -> Imported existingRootPkg
Just (Available rootPkgs) ->
if elem rootPkg rootPkgs
then Available rootPkgs
else Available $ insert rootPkg rootPkgs
-- make all exported identifiers within a package available for import
wildcardImports :: Identifier -> Scope ()
wildcardImports pkg = do
(exports, _) <- lift $ findPackage pkg
_ <- mapM (wildcardImport pkg) (Map.keys exports)
return ()
-- resolve and store an explicit (non-wildcard) import
explicitImport :: Identifier -> Identifier -> Scope ()
explicitImport pkg ident = do
rootPkg <- lift $ resolveRootPackage pkg ident
maybeIdentState <- lookupLocalIdentState ident
insertElem ident $
case maybeIdentState of
Nothing -> Imported rootPkg
Just Declared ->
error $ "import of " ++ pkg ++ "::" ++ ident
++ " conflicts with prior declaration of " ++ ident
Just Available{} -> Imported rootPkg
Just (Imported otherPkg) ->
if otherPkg == rootPkg
then Imported rootPkg
else error $ "import of " ++ pkg ++ "::" ++ ident
++ " conflicts with prior import of "
++ otherPkg ++ "::" ++ ident
-- main logic responsible for translating packages, resolving imports and
-- exports, and rewriting identifiers referring to package declarations
processItems :: Identifier -> Identifier -> [ModuleItem]
-> PackagesState (IdentStateMap, [ModuleItem])
processItems topName packageName moduleItems = do
(moduleItems', scopes) <- runScoperT $ scopeModuleItems scoper
topName (reorderItems moduleItems)
let rawIdents = extractMapping scopes
externalIdentMaps <- mapM (resolveExportMI rawIdents) moduleItems
let externalIdents = Map.unions externalIdentMaps
let declaredIdents = Map.filter isDeclared rawIdents
let exports = Map.union declaredIdents externalIdents
let exports' = if null packageName
then rawIdents
else exports
seq exports return (exports', moduleItems')
where
scoper = scopeModuleItem
traverseDeclM traverseModuleItemM traverseGenItemM traverseStmtM
-- produces partial mappings of exported identifiers, while also
-- checking the validity of the exports
resolveExportMI :: IdentStateMap -> ModuleItem -> PackagesState IdentStateMap
resolveExportMI mapping (MIPackageItem item@(Export pkg ident)) =
if null packageName
then error $ "invalid " ++ (init $ show item)
++ " outside of package"
else resolveExport mapping pkg ident
resolveExportMI _ _ = return Map.empty
-- declare an identifier, prefixing it if within a package
prefixIdent :: Identifier -> Scope Identifier
prefixIdent x = do
inProcedure <- withinProcedureM
maybeIdentState <- lookupLocalIdentState x
case maybeIdentState of
Just (Imported rootPkg) -> error $ "declaration of " ++ x
++ " conflicts with prior import of " ++ rootPkg
++ "::" ++ x
_ -> do
insertElem x Declared
if inProcedure || null packageName
then return x
else lift $ makeIdent packageName x
-- check the global scope for declarations or imports
resolveGlobalIdent :: Identifier -> Scope Identifier
resolveGlobalIdent x = do
(exports, _) <- lift $ findPackage ""
case Map.lookup x exports of
Nothing -> return x
Just identState -> do
-- inject the exact state outside of the module scope,
-- allowing wildcard imports to be handled correctly
insertElem [Access x Nil] identState
resolveIdent x
-- remap an identifier if needed based on declarations, explicit
-- imports, or names available for import
resolveIdent :: Identifier -> Scope Identifier
resolveIdent x = do
details <- lookupElemM x
case details of
Nothing ->
-- only missing identifiers within parts should be looked up
-- in the global scope
if null packageName && not (null topName)
then resolveGlobalIdent x
else return x
Just ([_, _], _, Declared) ->
if null packageName
then return x
else lift $ makeIdent packageName x
Just (_, _, Declared) -> return x
Just (_, _, Imported rootPkg) ->
lift $ makeIdent rootPkg x
Just (accesses, _, Available [rootPkg]) -> do
insertElem accesses $ Imported rootPkg
lift $ makeIdent rootPkg x
Just (_, _, Available rootPkgs) ->
error $ "identifier " ++ show x
++ " ambiguously refers to the definitions in any of "
++ intercalate ", " rootPkgs
traversePackageItemM :: PackageItem -> Scope PackageItem
traversePackageItemM orig@(Import pkg ident) = do
if null ident
then wildcardImports pkg
else explicitImport pkg ident
return $ Decl $ CommentDecl $ "removed " ++ show orig
traversePackageItemM orig@(Export pkg ident) = do
() <- when (not (null pkg || null ident)) $ do
localName <- resolveIdent ident
rootPkg <- lift $ resolveRootPackage pkg ident
localName `seq` rootPkg `seq` return ()
return $ Decl $ CommentDecl $ "removed " ++ show orig
traversePackageItemM other = return other
traverseDeclM :: Decl -> Scope Decl
traverseDeclM decl = do
decl' <- traverseDeclNodesM traverseTypeM traverseExprM decl
case decl' of
Variable d t x a e -> declHelp x $ \x' -> Variable d t x' a e
Net d n s t x a e -> declHelp x $ \x' -> Net d n s t x' a e
Param p t x e -> declHelp x $ \x' -> Param p t x' e
ParamType p x t -> declHelp x $ \x' -> ParamType p x' t
CommentDecl c -> return $ CommentDecl c
where declHelp x f = prefixIdent x >>= return . f
traverseRangeM :: Range -> Scope Range
traverseRangeM = mapBothM traverseExprM
traverseEnumItemM :: (Identifier, Expr) -> Scope (Identifier, Expr)
traverseEnumItemM (x, e) = do
x' <- prefixIdent x
e' <- traverseExprM e
return (x', e')
traverseTypeM :: Type -> Scope Type
traverseTypeM (CSAlias p b x rs) = do
x' <- resolveCSIdent' p b x
rs' <- mapM traverseRangeM rs
return $ Alias x' rs'
traverseTypeM (PSAlias p x rs) = do
x' <- resolvePSIdent' p x
rs' <- mapM traverseRangeM rs
return $ Alias x' rs'
traverseTypeM (Alias x rs) = do
rs' <- mapM traverseRangeM rs
x' <- resolveIdent x
return $ Alias x' rs'
traverseTypeM (Enum t enumItems rs) = do
t' <- traverseTypeM t
enumItems' <- mapM traverseEnumItemM enumItems
rs' <- mapM traverseRangeM rs
return $ Enum t' enumItems' rs'
traverseTypeM other =
traverseSinglyNestedTypesM traverseTypeM other
>>= traverseTypeExprsM traverseExprM
traverseExprM :: Expr -> Scope Expr
traverseExprM (CSIdent p b x) = do
x' <- resolveCSIdent' p b x
return $ Ident x'
traverseExprM (PSIdent p x) = do
x' <- resolvePSIdent' p x
return $ Ident x'
traverseExprM (Ident x) = resolveIdent x >>= return . Ident
traverseExprM other =
traverseSinglyNestedExprsM traverseExprM other
>>= traverseExprTypesM traverseTypeM
traverseLHSM :: LHS -> Scope LHS
traverseLHSM (LHSIdent x) = resolveIdent x >>= return . LHSIdent
traverseLHSM other = traverseSinglyNestedLHSsM traverseLHSM other
traverseGenItemM = traverseGenItemExprsM traverseExprM
traverseModuleItemM (MIPackageItem item) = do
item' <- traversePackageItemM item
return $ MIPackageItem item'
traverseModuleItemM other =
traverseModuleItemM' other
traverseModuleItemM' =
traverseTypesM traverseTypeM >=>
traverseExprsM traverseExprM >=>
traverseLHSsM traverseLHSM
traverseStmtM =
traverseStmtExprsM traverseExprM >=>
traverseStmtLHSsM traverseLHSM
2019-04-24 02:22:03 +02:00
traverseParamBindingM :: ParamBinding -> Scope ParamBinding
traverseParamBindingM (x, Left t) =
traverseTypeM t >>= return . (x, ) . Left
traverseParamBindingM (x, Right e) =
traverseExprM e >>= return . (x, ) . Right
-- wrapper allowing explicit reference to local package items
resolvePSIdent' :: Identifier -> Identifier -> Scope Identifier
resolvePSIdent' p x = do
if p /= packageName then
lift $ resolvePSIdent p x
else do
details <- lookupElemM $ Dot (Ident p) x
case details of
Just ([_, _], _, Declared) ->
lift $ makeIdent p x
Just ([_, _], _, Imported rootPkg) ->
lift $ makeIdent rootPkg x
_ -> error $ "package " ++ show p ++ " references"
++ " undeclared local \"" ++ p ++ "::" ++ x ++ "\""
-- wrapper resolving parameters and locally injecting the necessary
-- class items into modules and interfaces
resolveCSIdent'
:: Identifier -> [ParamBinding] -> Identifier -> Scope Identifier
resolveCSIdent' p b x = do
scopeKeys <- bindingsScopeKeys b
b' <- mapM traverseParamBindingM b
x' <- lift $ resolveCSIdent p b' scopeKeys x
let rootPkg = take (length x' - length x - 1) x'
when (null packageName) (classScopeInject rootPkg x')
return x'
-- inject the given class item and its dependencies into the local scope
classScopeInject :: Identifier -> Identifier -> Scope ()
classScopeInject rootPkg fullName = do
packages <- lift $ gets pkPackages
let (_, packageItems) = packages Map.! rootPkg
let localPIs = Map.fromList $ concatMap toPIElem packageItems
mapM_ injectIfMissing $
addItems localPIs Set.empty
[(Generate [], Set.singleton fullName)]
where
injectIfMissing :: ModuleItem -> Scope ()
injectIfMissing (Generate []) = return ()
injectIfMissing moduleItem = do
let MIPackageItem packageItem = moduleItem
let itemName : _ = piNames packageItem
details <- lookupElemM itemName
when (details == Nothing) $ do
accesses <- procedureLocM
let accesses' = accesses ++ [Access itemName Nil]
if null accesses
then insertElem itemName Declared
else insertElem accesses' Declared
injectItem moduleItem
toPIElem :: PackageItem -> [(Identifier, PackageItem)]
toPIElem item = map (, makeLocal item) (piNames item)
-- locate a package by name, processing its contents if necessary
findPackage :: Identifier -> PackagesState Package
findPackage packageName = do
PK { pkStack = stack, pkPackages = packages } <- get
let maybePackage = Map.lookup packageName packages
assertMsg (maybePackage /= Nothing) $
"could not find package " ++ show packageName
-- because this conversion doesn't enforce declaration ordering of packages,
-- it must check for dependency loops to avoid infinite recursion
let first : rest = reverse $ packageName : stack
assertMsg (not $ elem packageName stack) $
"package dependency loop: " ++ show first ++ " depends on "
++ intercalate ", which depends on " (map show rest)
let Just package@(exports, _) = maybePackage
if Map.null exports
then do
-- process and resolve this package
modify' $ \pk -> pk { pkStack = packageName : pkStack pk }
package' <- processPackage packageName $ snd package
pk <- get
let stack' = tail $ pkStack pk
let packages' = Map.insert packageName package' $ pkPackages pk
put $ pk { pkStack = stack', pkPackages = packages' }
return package'
else return package
-- helper for elaborating a package when it is first referenced
processPackage :: Identifier -> [PackageItem] -> PackagesState Package
processPackage packageName packageItems = do
(exports, moduleItems') <- processItems packageName packageName wrapped
let packageItems' = map unwrap moduleItems'
let package' = (exports, packageItems')
return package'
2019-04-24 02:22:03 +02:00
where
wrapped = map wrap packageItems
wrap :: PackageItem -> ModuleItem
wrap = MIPackageItem
unwrap :: ModuleItem -> PackageItem
unwrap packageItem = item
where MIPackageItem item = packageItem
-- resolve a package scoped identifier to its unique global name
resolvePSIdent :: Identifier -> Identifier -> PackagesState Identifier
resolvePSIdent packageName itemName = do
classes <- gets pkClasses
case Map.lookup packageName classes of
Nothing -> do
rootPkg <- resolveRootPackage packageName itemName
makeIdent rootPkg itemName
Just ([], _) -> resolveCSIdent packageName [] Set.empty itemName
Just _ -> error $ "reference to " ++ show itemName
++ " in parameterized class " ++ show packageName
++ " requires explicit #()"
-- determines the root package contained the given package scoped identifier
resolveRootPackage :: Identifier -> Identifier -> PackagesState Identifier
resolveRootPackage packageName itemName = do
(exports, _) <- findPackage packageName
let maybeIdentState = Map.lookup itemName exports
assertMsg (maybeIdentState /= Nothing) $
"could not find " ++ show itemName ++ " in package " ++ show packageName
let Just identState = maybeIdentState
return $ toRootPackage packageName identState
-- collect hashes of accessed resolved scopes in class parameters
bindingsScopeKeys :: [ParamBinding] -> Scope Idents
bindingsScopeKeys =
execWriterT . mapM (traverseTypeOrExprIdentsM identMapper) . map snd
where
identMapper :: Identifier -> WriterT Idents Scope Identifier
identMapper x = do
details <- lift $ lookupElemM x
case details of
Nothing -> return ()
Just (accesses, _, _) ->
tell $ Set.singleton $ shortHash accesses
return x
traverseTypeOrExprIdentsM mapper (Left t) =
traverseTypeIdentsM mapper t >>= return . Left
traverseTypeOrExprIdentsM mapper (Right e) =
traverseExprIdentsM mapper e >>= return . Right
-- resolve a class scoped identifier to its unique global name
resolveCSIdent :: Identifier -> [ParamBinding] -> Idents -> Identifier -> PackagesState Identifier
resolveCSIdent className paramBindings scopeKeys itemName = do
-- find the specified class
classes <- gets pkClasses
let maybeClass = Map.lookup className classes
assertMsg (maybeClass /= Nothing) $
"could not find class " ++ show className
let Just (classParams, classItems) = maybeClass
-- resolve the provided parameters
let resolveMsg = "parameters in class specialization of " ++ show className
let paramNames = mapMaybe extractParameterName classParams
let paramBindings' = resolveBindings resolveMsg paramNames paramBindings
-- generate a unique name for this synthetic package
let packageName = className ++ '_' : shortHash (scopeKeys, paramBindings')
-- process the synthetic package and inject the given parameters
(exports, classItems') <- processPackage packageName $
map Decl classParams ++ classItems
let overrider = overrideParam packageName paramBindings'
let classItems'' = map overrider classItems'
-- add the synthetic package to the state
let package = (exports, classItems'')
packages' <- gets $ Map.insert packageName package . pkPackages
modify' $ \pk -> pk { pkPackages = packages' }
-- ensure the item actually exists
let maybeIdentState = Map.lookup itemName exports
assertMsg (maybeIdentState /= Nothing) $
"could not find " ++ show itemName ++ " in class " ++ show className
makeIdent packageName itemName
where
extractParameterName :: Decl -> Maybe Identifier
extractParameterName (Param Parameter _ x _) = Just x
extractParameterName (ParamType Parameter x _) = Just x
extractParameterName _ = Nothing
-- replace default parameter values with the given overrides
overrideParam :: Identifier -> [ParamBinding] -> PackageItem -> PackageItem
overrideParam packageName bindings (Decl (Param Parameter t x e)) =
Decl $ Param Parameter t x $
case lookup x' bindings of
Just (Right e') -> e'
Just (Left t') ->
error $ "cannot override parameter " ++ show x'
++ " in class " ++ show className
++ " with type " ++ show t'
Nothing ->
if e == Nil
then error $ "required parameter " ++ show x'
++ " in class " ++ show className
++ " has not been provided"
else e
where x' = drop (1 + length packageName) x
overrideParam packageName bindings (Decl (ParamType Parameter x t)) =
Decl $ ParamType Parameter x $
case lookup x' bindings of
Just (Left t') -> t'
Just (Right e') ->
case exprToType e' of
Just t' -> t'
Nothing ->
error $ "cannot override type parameter "
++ show x' ++ " in class " ++ show className
++ " with expression " ++ show e'
Nothing ->
if t == UnknownType
then error $ "required type parameter " ++ show x'
++ " in class " ++ show className
++ " has not been provided"
else t
where x' = drop (1 + length packageName) x
overrideParam _ _ other = other
-- construct a new identifier for a package scoped identifier
makeIdent :: Identifier -> Identifier -> PackagesState Identifier
makeIdent x y = do
conflicts <- gets pkConflicts
return $ uniqueIdent conflicts $ x ++ '_' : y
-- prepend underscores until the name is unique
uniqueIdent :: Idents -> Identifier -> Identifier
uniqueIdent conflicts ident =
if Set.member ident conflicts
then uniqueIdent conflicts $ '_' : ident
else ident
-- errors with the given message when the check is false
assertMsg :: Monad m => Bool -> String -> m ()
assertMsg check msg = when (not check) $ error msg
-- helper for taking an ident which is either declared or exported form a
-- package and determine its true root source package
toRootPackage :: Identifier -> IdentState -> Identifier
toRootPackage sourcePackage identState =
if identState == Declared
then sourcePackage
else rootPackage
where Imported rootPackage = identState
-- nests packages items missing from modules
convertDescription :: PIs -> Description -> Description
convertDescription pis orig@Part{} =
if Map.null pis
then orig
else Part attrs extern kw lifetime name ports items'
where
Part attrs extern kw lifetime name ports items = orig
items' = addItems pis Set.empty $ map addUsedPIs $
foldr breakGenerate [] items -- ensure decls are visible
convertDescription _ other = other
-- attempt to fix simple declaration order issues
reorderItems :: [ModuleItem] -> [ModuleItem]
reorderItems items =
2021-01-25 17:18:16 +01:00
addItems localPIs Set.empty $ map addUsedPIs $
map (traverseGenItems $ traverseNestedGenItems reorderGenItem) items
where
localPIs = Map.fromList $ concat $ mapMaybe toPIElem items
toPIElem :: ModuleItem -> Maybe [(Identifier, PackageItem)]
toPIElem (MIPackageItem item) = Just $ map (, item) (piNames item)
toPIElem _ = Nothing
2021-01-25 17:18:16 +01:00
-- attempt to declaration order issues within generate blocks
reorderGenItem :: GenItem -> GenItem
reorderGenItem (GenBlock name genItems) =
GenBlock name $ map unwrap $ reorderItems $ map wrap genItems
where
wrap :: GenItem -> ModuleItem
wrap (GenModuleItem item) = item
wrap item = Generate [item]
unwrap :: ModuleItem -> GenItem
unwrap (Generate [item]) = item
unwrap item = GenModuleItem item
reorderGenItem item = item
-- iteratively inserts missing package items exactly where they are needed
addItems :: PIs -> Idents -> [(ModuleItem, Idents)] -> [ModuleItem]
addItems pis existingPIs ((item, usedPIs) : items) =
if not $ forceKeep || Set.disjoint existingPIs thisPI then
-- this item was re-imported earlier in the module
addItems pis existingPIs items
else if Map.null itemsToAdd then
-- this item has no additional dependencies
item : addItems pis (Set.union existingPIs thisPI) items
else
-- this item has at least one un-met dependency
addItems remainingPIs existingPIs
(addUsedPIs chosenItem : (item, usedPIs) : items)
2019-04-24 02:22:03 +02:00
where
thisPI = case item of
MIPackageItem packageItem ->
Set.fromList $ piNames packageItem
Instance _ _ x _ _ -> Set.singleton x
_ -> Set.empty
forceKeep = case item of
Instance{} -> True
_ -> False
neededPIs = Set.difference (Set.difference usedPIs existingPIs) thisPI
itemsToAdd = Map.restrictKeys pis neededPIs
(chosenName, chosenPI) = Map.findMin itemsToAdd
chosenItem = MIPackageItem chosenPI
remainingPIs = Map.delete chosenName pis
addItems _ _ [] = []
-- augment a module item with the set of identifiers it uses
addUsedPIs :: ModuleItem -> (ModuleItem, Idents)
addUsedPIs item =
(item, usedPIs)
where
usedPIs = execWriter $ evalScoperT $ scoper item
scoper = scopeModuleItem writeDeclIdents writeModuleItemIdents
writeGenItemIdents writeStmtIdents
type IdentWriter = ScoperT () (Writer Idents)
writeDeclIdents :: Decl -> IdentWriter Decl
writeDeclIdents decl = do
case decl of
Variable _ _ x _ _ -> insertElem x ()
Net _ _ _ _ x _ _ -> insertElem x ()
Param _ _ x _ -> insertElem x ()
ParamType _ x _ -> insertElem x ()
CommentDecl{} -> return ()
traverseDeclIdentsM writeIdent decl
writeModuleItemIdents :: ModuleItem -> IdentWriter ModuleItem
writeModuleItemIdents = traverseIdentsM writeIdent
writeGenItemIdents :: GenItem -> IdentWriter GenItem
writeGenItemIdents =
traverseGenItemExprsM $ traverseExprIdentsM writeIdent
writeStmtIdents :: Stmt -> IdentWriter Stmt
writeStmtIdents = traverseStmtIdentsM writeIdent
writeIdent :: Identifier -> IdentWriter Identifier
writeIdent x = do
details <- lookupElemM x
when (details == Nothing) $ tell (Set.singleton x)
return x
-- visits all identifiers in a module item
traverseIdentsM :: Monad m => MapperM m Identifier -> MapperM m ModuleItem
traverseIdentsM identMapper = traverseNodesM
(traverseExprIdentsM identMapper)
(traverseDeclIdentsM identMapper)
(traverseTypeIdentsM identMapper)
(traverseLHSIdentsM identMapper)
(traverseStmtIdentsM identMapper)
-- visits all identifiers in an expression
traverseExprIdentsM :: Monad m => MapperM m Identifier -> MapperM m Expr
traverseExprIdentsM identMapper = fullMapper
where
fullMapper = exprMapper >=> traverseSinglyNestedExprsM fullMapper
exprMapper (Call (Ident x) args) =
identMapper x >>= \x' -> return $ Call (Ident x') args
exprMapper (Ident x) = identMapper x >>= return . Ident
exprMapper other = return other
-- visits all identifiers in a type
traverseTypeIdentsM :: Monad m => MapperM m Identifier -> MapperM m Type
traverseTypeIdentsM identMapper = fullMapper
where
fullMapper = typeMapper
>=> traverseTypeExprsM (traverseExprIdentsM identMapper)
>=> traverseSinglyNestedTypesM fullMapper
typeMapper (Alias x t) = identMapper x >>= return . flip Alias t
typeMapper other = return other
-- visits all identifiers in an LHS
traverseLHSIdentsM :: Monad m => MapperM m Identifier -> MapperM m LHS
traverseLHSIdentsM identMapper = fullMapper
where
fullMapper = lhsMapper
>=> traverseLHSExprsM (traverseExprIdentsM identMapper)
>=> traverseSinglyNestedLHSsM fullMapper
lhsMapper (LHSIdent x) = identMapper x >>= return . LHSIdent
lhsMapper other = return other
-- visits all identifiers in a statement
traverseStmtIdentsM :: Monad m => MapperM m Identifier -> MapperM m Stmt
traverseStmtIdentsM identMapper = fullMapper
where
fullMapper = stmtMapper
>=> traverseStmtExprsM (traverseExprIdentsM identMapper)
>=> traverseStmtLHSsM (traverseLHSIdentsM identMapper)
stmtMapper (Subroutine (Ident x) args) =
identMapper x >>= \x' -> return $ Subroutine (Ident x') args
stmtMapper other = return other
-- visits all identifiers in a declaration
traverseDeclIdentsM :: Monad m => MapperM m Identifier -> MapperM m Decl
traverseDeclIdentsM identMapper = traverseDeclNodesM
(traverseTypeIdentsM identMapper)
(traverseExprIdentsM identMapper)
-- returns any names defined by a package item
piNames :: PackageItem -> [Identifier]
2021-01-24 18:07:35 +01:00
piNames (Decl (ParamType _ ident (Enum _ enumItems _))) =
ident : map fst enumItems
piNames (Function _ _ ident _ _) = [ident]
piNames (Task _ ident _ _) = [ident]
piNames (Decl (Variable _ _ ident _ _)) = [ident]
piNames (Decl (Net _ _ _ _ ident _ _)) = [ident]
piNames (Decl (Param _ _ ident _)) = [ident]
piNames (Decl (ParamType _ ident _)) = [ident]
piNames (Decl (CommentDecl _)) = []
piNames item@DPIImport{} = [show item]
piNames item@DPIExport{} = [show item]
piNames item@Import{} = [show item]
piNames item@Export{} = [show item]
piNames (Directive _) = []