diff --git a/BACKLOG.md b/BACKLOG.md index 2135c0b..18ceb63 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -36,4 +36,22 @@ in the call tree. } -Write graphviz output. +The above problem is sort of fixed by just writing DOT output as it +takes care of cycles. + +# Instance methods +Instance methods are a common occurrence in Ruby code. However they +aren't really resolved in a nice way. E.g.: + + def f + person.name + end + +Will produce: + + {"f":["name"]} + +Which is misleading. What we could do it try and find out what +`person` is referring to and then "inline" it. For example. Assume +that we have `"person": ["Person.new"]` in our references then we +could add an entry for `f` like so: `"f": ["Person.name"]`. diff --git a/package.yaml b/package.yaml index 8e49fd0..4025c37 100644 --- a/package.yaml +++ b/package.yaml @@ -35,6 +35,7 @@ dependencies: - temporary - text - hashable + - scientific default-extensions: - ConstraintKinds diff --git a/src/Data/Language/Ruby/AST.hs b/src/Data/Language/Ruby/AST.hs index 975d1ce..b5ba0d1 100644 --- a/src/Data/Language/Ruby/AST.hs +++ b/src/Data/Language/Ruby/AST.hs @@ -20,16 +20,19 @@ module Data.Language.Ruby.AST , Ivar(..) , Atom(..) , Defs(..) - , Self - , Nil - , Cbase + , Self(..) + , Nil(..) + , Cbase(..) + , Int(..) ) where import Data.Aeson (parseJSON, Value(Null,Object,Number,Bool), withArray) -import Frelude hiding (String) +import Prelude (Bounded) +import Frelude hiding (String, Int) import qualified Frelude import qualified Data.Aeson.Types as Aeson import qualified Data.Vector as Vector +import qualified Data.Scientific as Scientific kebabCase :: Frelude.String -> Frelude.String kebabCase = Aeson.camelTo2 '-' @@ -73,6 +76,7 @@ data Statement | StmtIvar Ivar | StmtSelf Self | StmtNil Nil + | StmtInt Int | StmtCbase Cbase -- TODO Get rid of this | StmtAnything Anything @@ -101,6 +105,7 @@ instance ToJSON Statement where "StmtIvar" -> "ivar" "StmtSelf" -> "self" "StmtNil" -> "nil" + "StmtInt" -> "int" "StmtCbase" -> "cbase" x -> x @@ -121,6 +126,7 @@ instance FromJSON Statement where <|> (StmtIvar <$> parseJSON v) <|> (StmtSelf <$> parseJSON v) <|> (StmtNil <$> parseJSON v) + <|> (StmtInt <$> parseJSON v) <|> (StmtCbase <$> parseJSON v) <|> (StmtAnything <$> parseJSON v) @@ -486,3 +492,29 @@ instance FromJSON Nil where parseJSON = \case Aeson.Null -> pure Nil _ -> empty + + +newtype Int = Int Frelude.Int + +deriving stock instance Show Int +deriving newtype instance Ord Int +deriving newtype instance Eq Int +deriving stock instance Generic Int +deriving newtype instance Enum Int +deriving newtype instance Num Int +deriving newtype instance Real Int +deriving newtype instance Integral Int +deriving newtype instance Bounded Int +instance ToJSON Int where + toEncoding = Aeson.genericToEncoding aesonOptions +instance FromJSON Int where + parseJSON = withArray "Int" $ \case + [Aeson.String "int", atom] -> parseIntegral atom + _ -> empty + +parseIntegral :: forall f n . Alternative f => Bounded n => Integral n => Value -> f n +parseIntegral = \case + Aeson.Number s -> case Scientific.toBoundedInteger @n s of + Nothing -> empty + Just n -> pure $ fromIntegral n + _ -> empty diff --git a/src/Rubyhs/References.hs b/src/Rubyhs/References.hs index 70d0708..200024b 100644 --- a/src/Rubyhs/References.hs +++ b/src/Rubyhs/References.hs @@ -138,8 +138,8 @@ instance Semigroup Node where (NodeModule n0, NodeModule n1) -> NodeModule $ n0 <> n1 -- (NodeModule n0, NodeDef (FQN n1 f)) -> Context $ NodeDef $ FQN (n0 <> n1) f (NodeModule n0, NodeDef (FQN n1 f)) -> NodeDef $ FQN (n0 <> n1) f - (NodeDef{}, NodeModule{}) -> error "Cannot append module to function context." - -- (NodeDef{}, NodeDef{}) -> error "Cannot append function to function context." + -- TODO: Problematic: Is a Node really a semigroup with this representation? + (NodeDef{}, NodeModule{}) -> c1 (NodeDef (FQN n0 _), NodeDef (FQN n1 a1)) -> NodeDef $ FQN (n0 <> n1) a1 class Monad m => MyMonad (m :: Type -> Type) where @@ -250,6 +250,7 @@ instance References Ruby.Statement where Ruby.StmtSelf s -> entries s Ruby.StmtCbase s -> entries s Ruby.StmtNil n -> entries n + Ruby.StmtInt n -> entries n Ruby.StmtAnything a -> entries a instance References Ruby.Block where @@ -268,7 +269,10 @@ instance References Ruby.Sym where instance References Ruby.Str where entries _ = pure () instance References Ruby.Lvasgn where - entries _ = pure () + entries (Ruby.Lvasgn _ b) = entries b +-- We don't consider "local variables". We could do though and say +-- e.g. the local variable is a `Node` with the same "context" as the +-- caller. instance References Ruby.Lvar where entries _ = pure () instance References Ruby.Ivar where @@ -283,6 +287,8 @@ instance References Ruby.Array where entries Ruby.Array{statements} = traverse_ entries statements instance References Ruby.Anything where entries = const $ pure () +instance References Ruby.Int where + entries = const $ pure () instance References Ruby.Const where entries con = application $ NodeModule $ constToNamespace con @@ -344,7 +350,10 @@ statementToNamespace = go mempty Ruby.StmtSend{} -> acc Ruby.StmtIvar{} -> acc Ruby.StmtLvar{} -> acc - _ -> error "Can only build namespaces from sequences of `const` statements" + Ruby.StmtArray{} -> acc + -- TODO: Catch-all. Is this what we want to do? See e.g. the + -- note about instance methods in the backlog. + _ -> acc constToNamespace :: Ruby.Const -> Namespace constToNamespace Ruby.Const{context, atom} = statementToNamespace context <> [k] diff --git a/test/Main.hs b/test/Main.hs index afc62eb..dc15a92 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -17,7 +17,7 @@ main :: IO () main = withCurrentDirectory "test" $ defaultMain $ testGroup "Unit tests" tests tests :: [TestTree] -tests = go <$> ["mod", "simple", "nested", "closest"] +tests = go <$> ["mod", "simple", "nested", "closest", "assignment"] where go :: String -> TestTree go s = testCase s $ do diff --git a/test/tests/assignment.json b/test/tests/assignment.json new file mode 100644 index 0000000..3a5362d --- /dev/null +++ b/test/tests/assignment.json @@ -0,0 +1,5 @@ +{ + "": [ + "A" + ] +} diff --git a/test/tests/assignment.rb b/test/tests/assignment.rb new file mode 100644 index 0000000..c415986 --- /dev/null +++ b/test/tests/assignment.rb @@ -0,0 +1,3 @@ +A + +b = A