rubyhs/src/Rubyhs/References.hs

284 lines
8.8 KiB
Haskell
Raw Normal View History

2019-10-11 09:08:36 +00:00
{-# LANGUAGE DuplicateRecordFields, OverloadedLists, InstanceSigs #-}
{-# OPTIONS_GHC -Wall #-}
2019-10-03 14:13:34 +00:00
module Rubyhs.References
( References(entries)
, FQN(..)
, references
2019-10-11 09:08:36 +00:00
, Env(..)
2019-10-11 09:37:11 +00:00
, Result(..)
, Context(..)
2019-10-16 18:12:07 +00:00
, graph
, prettyContext
, Node(..)
2019-10-03 14:13:34 +00:00
) where
import Frelude
-- import Data.Language.Ruby hiding (context)
2019-10-11 12:03:01 +00:00
import qualified Data.Language.Ruby as Ruby
2019-10-11 09:08:36 +00:00
import Data.Map (Map)
2019-10-03 14:13:34 +00:00
import qualified Data.Map as Map
import Data.Kind
import Control.Monad.State
import qualified Data.Aeson as Aeson
2019-10-11 12:03:01 +00:00
import Data.Set (Set)
import qualified Data.Set as Set
2019-10-15 18:46:28 +00:00
import qualified Data.Text as Text
import Data.Coerce
2019-10-16 18:12:07 +00:00
import Data.Graph (Graph, Vertex, Forest)
import qualified Data.Graph as Graph
span :: G node key -> Forest node
span (g, f, _) = fmap ((\(x, _, _) -> x) . f) <$> Graph.dff g
type G node key = (Graph, Vertex -> (node, key, [key]), key -> Maybe Vertex)
graph :: Ruby.Begin -> Forest Node
2019-10-16 18:12:07 +00:00
graph b = span $ Graph.graphFromEdges $ go <$> toList ys
where
go :: (Node, Set Node) -> (Node, Text, [Text])
go (x, xs) = (x, prettyContext x, prettyContext <$> toList xs)
Result ys = references b
2019-10-03 14:13:34 +00:00
2019-10-11 12:03:01 +00:00
newtype Namespace = Namespace [Text]
deriving stock instance Show Namespace
deriving stock instance Eq Namespace
deriving stock instance Ord Namespace
instance IsList Namespace where
type Item Namespace = Text
2019-10-15 19:50:19 +00:00
fromList = coerce
toList = coerce
2019-10-11 12:03:01 +00:00
deriving newtype instance Semigroup Namespace
deriving newtype instance Monoid Namespace
2019-10-15 18:46:28 +00:00
instance ToJSON Namespace where
2019-10-15 19:50:19 +00:00
toJSON = Aeson.String . showNamespace
showNamespace :: Namespace -> Text
showNamespace = Text.intercalate "::" . coerce
2019-10-11 12:03:01 +00:00
2019-10-11 09:37:11 +00:00
-- Names are in reverse order.
2019-10-11 12:03:01 +00:00
data FQN = FQN
{ namespace :: Namespace
, name :: Text
}
2019-10-03 14:13:34 +00:00
data Node = NodeDef FQN | NodeModule Namespace
2019-10-15 18:46:28 +00:00
deriving stock instance Show Node
deriving stock instance Eq Node
deriving stock instance Ord Node
instance ToJSON Node where
toJSON = \case
NodeDef q -> Aeson.toJSON q
2019-10-15 18:46:28 +00:00
NodeModule m -> Aeson.toJSON m
2019-10-11 09:08:36 +00:00
deriving stock instance Show FQN
2019-10-11 12:03:01 +00:00
deriving stock instance Eq FQN
deriving stock instance Ord FQN
instance Pretty Namespace where
2019-10-15 19:50:19 +00:00
pretty = pretty . showNamespace
2019-10-11 12:03:01 +00:00
instance Aeson.ToJSON FQN where
toJSON = Aeson.String . prettyFQN
2019-10-11 09:08:36 +00:00
instance Aeson.ToJSONKey FQN where
instance Pretty FQN where
2019-10-11 12:03:01 +00:00
pretty = pretty . prettyFQN
prettyFQN :: FQN -> Text
2019-10-15 18:46:28 +00:00
prettyFQN FQN{name, namespace} = case namespace of
2019-10-11 12:03:01 +00:00
[] -> name
2019-10-15 19:50:19 +00:00
_ -> convertString (showNamespace namespace) <> "." <> name
2019-10-03 14:13:34 +00:00
2019-10-15 18:46:28 +00:00
newtype Context = Context Node
deriving stock instance Eq Context
deriving stock instance Ord Context
2019-10-15 18:46:28 +00:00
deriving newtype instance ToJSON Context
-- HACK: Not really a semigroup as is evident from the implementation.
instance Semigroup Context where
Context c0 <> Context c1 = case (c0, c1) of
(NodeModule n0, NodeModule n1) -> Context $ NodeModule $ n0 <> n1
(NodeModule n0, NodeDef (FQN n1 f)) -> Context $ NodeDef $ FQN (n0 <> n1) f
(NodeDef{}, NodeModule{}) -> error "Cannot append module to function context."
(NodeDef{}, NodeDef{}) -> error "Cannot append function to function context."
2019-10-03 14:13:34 +00:00
class Monad m => MyMonad (m :: Type -> Type) where
2019-10-15 18:46:28 +00:00
declaration :: Node -> m ()
2019-10-16 20:43:19 +00:00
application :: Node -> m ()
getContext :: m Context
writeContext :: Context -> m ()
2019-10-03 14:13:34 +00:00
data Env = Env
2019-10-15 18:46:28 +00:00
{ declarations :: Set Node
, applications :: Map Node (Set Node)
, context :: Context
2019-10-03 14:13:34 +00:00
}
2019-10-16 18:12:07 +00:00
newtype Result = Result (Map Node (Set Node))
2019-10-11 09:37:11 +00:00
instance ToJSON Result where
2019-10-16 18:12:07 +00:00
toJSON (Result a) = Aeson.Object $ fromList $ go <$> toList a
2019-10-11 09:37:11 +00:00
where
2019-10-16 18:12:07 +00:00
go :: (Node, Set Node) -> (Text, Aeson.Value)
go (x, y) = (prettyContext x, Aeson.toJSON y)
2019-10-11 09:37:11 +00:00
2019-10-15 18:46:28 +00:00
prettyContext :: Node -> Text
prettyContext = \case
NodeDef fun -> prettyFQN fun
2019-10-15 19:50:19 +00:00
NodeModule ns -> showNamespace ns
2019-10-03 14:13:34 +00:00
instance MyMonad (State Env) where
2019-10-11 12:03:01 +00:00
declaration q = modify go
2019-10-03 14:13:34 +00:00
where
2019-10-11 09:37:11 +00:00
go :: Env -> Env
2019-10-11 12:03:01 +00:00
go env@Env{declarations} = env { declarations = Set.insert q declarations }
2019-10-16 20:43:19 +00:00
application n = do
Context c <- getContext
let
go env@Env{applications}
= env { applications = Map.insertWith mappend c (Set.singleton n) applications }
modify go
getContext = gets Rubyhs.References.context
2019-10-03 14:13:34 +00:00
writeContext q = modify go
where
go env = env { Rubyhs.References.context = q }
2019-10-03 14:13:34 +00:00
locally :: MyMonad m => m a -> m a
locally act = do
old <- getContext
res <- act
writeContext old
pure res
class References a where
entries :: MyMonad m => a -> m ()
references :: Ruby.Begin -> Result
2019-10-16 18:12:07 +00:00
references q = Result $ Map.unionWith mappend applications declarations'
2019-10-11 09:37:11 +00:00
where
Env{declarations,applications} = execState (entries @_ @(State Env) q) emptyEnv
2019-10-15 18:46:28 +00:00
emptyEnv = Env mempty mempty (Context $ NodeModule mempty)
2019-10-16 18:12:07 +00:00
declarations' = fromList $ (\x -> (x, mempty)) <$> toList declarations
2019-10-03 14:13:34 +00:00
instance References Ruby.Begin where
entries :: forall m . MyMonad m => Ruby.Begin -> m ()
entries (Ruby.Begin defs) = traverse_ (locally . entries) defs
2019-10-03 14:13:34 +00:00
instance References Ruby.Statement where
2019-10-03 14:13:34 +00:00
entries = \case
Ruby.StmtModule m -> entries m
Ruby.StmtDef f -> entries f
Ruby.StmtDefs f -> entries f
Ruby.StmtSend s -> entries s
Ruby.StmtConst c -> entries c
Ruby.StmtBlock b -> entries b
Ruby.StmtCasgn c -> entries c
Ruby.StmtArray a -> entries a
Ruby.StmtSym s -> entries s
Ruby.StmtStr s -> entries s
Ruby.StmtLvasgn a -> entries a
Ruby.StmtLvar a -> entries a
Ruby.StmtIvar a -> entries a
Ruby.StmtSelf s -> entries s
Ruby.StmtCbase s -> entries s
Ruby.StmtNil n -> entries n
Ruby.StmtAnything a -> entries a
2019-10-16 20:12:30 +00:00
2019-10-17 18:08:25 +00:00
instance References Ruby.Block where
entries Ruby.Block{send,args,begin} = do
entries send
entries args
2019-10-17 18:08:25 +00:00
entries begin
instance References Ruby.Args where
2019-10-16 20:12:30 +00:00
entries = const $ pure ()
2019-10-16 21:23:40 +00:00
-- TODO: We have to make a "declaration" for the constant here as
-- well!
2019-10-16 20:12:30 +00:00
instance References Ruby.Casgn where
entries Ruby.Casgn{rhs} = entries rhs
2019-10-17 18:08:25 +00:00
instance References Ruby.Sym where
entries _ = pure ()
instance References Ruby.Str where
entries _ = pure ()
instance References Ruby.Lvasgn where
entries _ = pure ()
instance References Ruby.Lvar where
entries _ = pure ()
instance References Ruby.Ivar where
entries _ = pure ()
instance References Ruby.Self where
entries _ = pure ()
instance References Ruby.Cbase where
entries _ = pure ()
instance References Ruby.Nil where
entries _ = pure ()
instance References Ruby.Array where
entries Ruby.Array{statements} = traverse_ entries statements
2019-10-16 20:12:30 +00:00
instance References Ruby.Anything where
entries = const $ pure ()
2019-10-03 14:13:34 +00:00
instance References Ruby.Const where
entries con = application $ NodeModule $ constToNamespace con
updateContext :: MyMonad m => (Context -> Context) -> m ()
updateContext f = getContext >>= \c -> writeContext (f c)
instance References Ruby.Module where
entries Ruby.Module{name, begin} = do
updateContext $ (<>) $ Context $ NodeModule $ constToNamespace name
2019-10-15 18:46:28 +00:00
c <- getContext >>= \case
Context (NodeModule c) -> pure c
_ -> error "..."
declaration $ NodeModule c
2019-10-17 18:08:25 +00:00
entries begin
atomToNode :: MyMonad m => Ruby.Atom -> m Node
atomToNode (Ruby.Atom name) = do
2019-10-17 18:08:25 +00:00
namespace <- getContext >>= \case
Context (NodeModule c) -> pure c
-- We could allow this and just say that the function defined in
-- another function sits in the same context as the surrounding
-- function.
Context NodeDef{} -> error "Cannot have a function declaration in a function context"
pure $ NodeDef $ FQN namespace name
instance References Ruby.Def where
entries Ruby.Def{atom, begin} = do
node <- atomToNode atom
2019-10-17 18:08:25 +00:00
declaration node
locally $ do
2019-10-17 18:08:25 +00:00
writeContext (Context node)
entries begin
2019-10-03 14:13:34 +00:00
instance References Ruby.Defs where
-- TODO: The field `context` is also relevant here!
entries Ruby.Defs{atom, begin}
= entries $ Ruby.Def{atom,begin,args=error "hack"}
2019-10-15 19:50:19 +00:00
qual :: Namespace -> Ruby.Atom -> FQN
qual namespace (Ruby.Atom name) = FQN { namespace , name }
2019-10-15 19:50:19 +00:00
instance References Ruby.Send where
entries Ruby.Send{context, atom, args} = do
application $ NodeDef $ qual (statementToNamespace context) atom
traverse_ entries args
2019-10-15 19:50:19 +00:00
statementToNamespace :: Ruby.Statement -> Namespace
statementToNamespace = go mempty
where
go acc = \case
Ruby.StmtConst c -> acc <> constToNamespace c
-- The nil-case and cbase-case should produce different results,
-- surely. `Namespace` may not be a good representation.
Ruby.StmtNil{} -> acc
Ruby.StmtCbase{} -> acc
-- The send-, ivar- and lvar- case cannot be handled because of the way
-- we've defined `Namespace`.
Ruby.StmtSend{} -> acc
Ruby.StmtIvar{} -> acc
Ruby.StmtLvar{} -> acc
_ -> error "Can only build namespaces from sequences of `const` statements"
constToNamespace :: Ruby.Const -> Namespace
constToNamespace Ruby.Const{context, atom} = statementToNamespace context <> [k]
where
Ruby.Atom k = atom