descript-lang-0.2.0.0: src/Descript/Misc/Build/Read/File/Scope.hs
module Descript.Misc.Build.Read.File.Scope
( AbsScope (..)
, RelScope (..)
, baseScope
, undefinedScope
, anchorScopeSib
, anchorScopeParent
, scopeRelToSib
, scopeRelToParent
, globalizeRelScope
, scopeFilepath
) where
import Descript.Misc.Summary
import Data.List
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import qualified Core.Data.List.NonEmpty as NonEmpty
import System.FilePath
import Core.System.FilePath
-- | The name of the module\/file where an identifier is located.
-- If the identifier is imported from another module, the imported
-- reducers should automatically alter between the old and new module
-- scopes.
newtype AbsScope
= AbsScope
{ absScopePath :: NonEmpty String -- ^ The path - \outermost element first\.
} deriving (Eq, Ord, Read, Show)
-- | The scope of a module\/file relative to another module\/file.
data RelScope
= RelScope
{ relScopePath :: NonEmpty String -- ^ Path elements prepended to the anchor path.
, relScopeUps :: Int -- ^ Path elements removed from the head of the anchor path.
} deriving (Eq, Ord, Read, Show)
instance Summary AbsScope where
summary (AbsScope path) = pathSummary path
instance Summary RelScope where
summary (RelScope path ups) = upsSummary ups ++ pathSummary path
-- | The scope containing the most primitive imports, such as code
-- blocks.
baseScope :: AbsScope
baseScope = AbsScope $ "Base" :| []
-- | The scope for unresolved symbols.
undefinedScope :: AbsScope
undefinedScope = AbsScope $ "{undefined}" :| []
-- | The given relative scope made absolute "relative to" the given
-- sibling. -- The semantics are a bit confusing, also see
-- 'anchorScopeParent', here are some examples:
--
-- >>> "Foo>Bar>Baz" `anchorScopeSib` "Qux"
-- "Foo>Bar>Qux"
-- >>> "Foo>Bar>Baz" `anchorScopeSib` "<Qux"
-- "Foo>Qux"
-- >>> "Foo>Bar" `anchorScopeSib` "Baz>Qux"
-- "Foo>Baz>Qux"
anchorScopeSib :: AbsScope -> RelScope -> AbsScope
anchorScopeSib (AbsScope basePath) (RelScope relPath relUps)
= AbsScope
{ absScopePath = NonEmpty.dropEnd (relUps + 1) basePath NonEmpty.|+ relPath
}
-- | The given relative scope made absolute "relative to" the given
-- parent. -- The semantics are a bit confusing, also see
-- 'anchorScopeSib', here are some examples:
--
-- >>> "Foo>Bar>Baz" `anchorScopeParent` "Qux"
-- "Foo>Bar>Baz>Qux"
-- >>> "Foo>Bar>Baz" `anchorScopeParent` "<Qux"
-- "Foo>Bar>Qux"
-- >>> "Foo>Bar" `anchorScopeParent` "Baz>Qux"
-- "Foo>Bar>Baz>Qux"
anchorScopeParent :: AbsScope -> RelScope -> AbsScope
anchorScopeParent (AbsScope basePath) (RelScope relPath relUps)
= AbsScope
{ absScopePath = NonEmpty.dropEnd relUps basePath NonEmpty.|+ relPath
}
-- | The first absolute scope made "relative to" the second scope
-- (anchor), so that it's an immediate scope if the anchor is its
-- sibling (e.g. in the same directory).
-- The semantics are a bit confusing, also see 'scopeRelToParent',
-- here are some examples:
--
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Bar>Qux"
-- "Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Qux"
-- "Bar>Baz"
-- >>> "Foo>Bar" `scopeRelToSib` "Foo>Baz>Qux"
-- "<Bar"
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Qux>Abc"
-- "<Bar>Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Bar>Baz"
-- "Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Bar"
-- "Bar>Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToSib` "Foo>Bar>Baz>Qux"
-- ""
scopeRelToSib :: AbsScope -> AbsScope -> RelScope
AbsScope tpath `scopeRelToSib` AbsScope apath
= tpath `subpathRelTo` NonEmpty.init apath
-- | The first absolute scope made "relative to" the second scope
-- (anchor), so that it's an immediate scope if the anchor is its
-- parent (e.g. in the parent directory, with the same name as the
-- child directory).
-- The semantics are a bit confusing, also see 'scopeRelToSib', here are
-- some examples:
--
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Bar>Qux"
-- "<Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Qux"
-- "<Bar>Baz"
-- >>> "Foo>Bar" `scopeRelToParent` "Foo>Baz>Qux"
-- "<<Bar"
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Qux>Abc"
-- "<<Bar>Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Bar>Baz"
-- "<Baz" -- Special case - scope needs to be 'NonEmpty'
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Bar"
-- "Baz"
-- >>> "Foo>Bar>Baz" `scopeRelToParent` "Foo>Bar>Baz>Qux"
-- "<<Bar" -- Special case - scope needs to be 'NonEmpty'
scopeRelToParent :: AbsScope -> AbsScope -> RelScope
AbsScope tpath `scopeRelToParent` AbsScope apath
= tpath `subpathRelTo` NonEmpty.toList apath
-- | The first path relative to the second path as a parent.
subpathRelTo :: NonEmpty String -> [String] -> RelScope
xs `subpathRelTo` []
= RelScope
{ relScopePath = xs
, relScopeUps = 0
}
(x :| xs) `subpathRelTo` (y : ys)
| x /= y || xs == []
= RelScope
{ relScopePath = x :| xs
, relScopeUps = length $ y : ys
}
| otherwise
= NonEmpty.fromList xs `subpathRelTo` ys
-- | Gets rid of 'relScopeUps'.
globalizeRelScope :: RelScope -> RelScope
globalizeRelScope (RelScope path _) = RelScope path 0
-- | The path to get to the file/module at the scope, given the path to
-- the base scope, used by the local dependency resolver.
--
-- >>> scopeFilepath "foo/Bar" "Baz"
-- foo/Bar/Baz.dscr
-- >>> scopeFilepath "foo/Bar" "<Baz>Qux"
-- foo/Baz/Qux.dscr
scopeFilepath :: FilePath -> RelScope -> FilePath
scopeFilepath basePath (RelScope relPath relUps)
= takeDirectoryN relUps basePath
</> intercalate [pathSeparator] (NonEmpty.toList relPath)
<.> "dscr"
upsSummary :: Int -> String
upsSummary ups = replicate ups '<'
pathSummary :: NonEmpty String -> String
pathSummary = intercalate ">" . NonEmpty.toList