[LIGO-41] Convert ligo scopes to ScopedDecl

This commit is contained in:
Anton Myasnikov 2020-09-03 15:18:56 +03:00
parent d5154dff36
commit 619a7de432
No known key found for this signature in database
GPG Key ID: FEB685E6DAA0A95F
2 changed files with 181 additions and 54 deletions

View File

@ -1,18 +1,24 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric, DerivingVia, RecordWildCards #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE RecordWildCards #-}
-- | Module that handles ligo binary execution. -- | Module that handles ligo binary execution.
module Cli.Impl where module Cli.Impl
( LigoError(..)
, callLigo
, parseLigoScopes
, parseLigoTypesFor
, getLigoScopesRaw
, getLigoScopedDecls
, getLigoTypesFor
, parseScopedDecls
) where
import Cli.Json
import Cli.Types
import Control.Exception (Exception (..), IOException, catch, throwIO) import Control.Exception (Exception (..), IOException, catch, throwIO)
import Control.Lens hiding ((<.>)) import Control.Lens hiding ((<.>))
import Control.Monad.Catch (MonadThrow (throwM)) import Control.Monad.Catch (MonadThrow (throwM))
import Data.Aeson import Data.Aeson
import Data.Aeson.Lens import Data.Aeson.Lens
import qualified Data.ByteString.Lazy.Char8 as C8 import qualified Data.ByteString.Lazy.Char8 as C8
import qualified Data.HashMap.Strict as HM
import Data.Text import Data.Text
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import Duplo.Pretty import Duplo.Pretty
@ -20,6 +26,10 @@ import System.Exit
import System.IO import System.IO
import System.Process (readProcessWithExitCode) import System.Process (readProcessWithExitCode)
import AST.Scope
import Cli.Json
import Cli.Types
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- Errors -- Errors
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
@ -118,13 +128,51 @@ readProcessWithExitCode' fp args inp =
-- Execution -- Execution
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Parse from output file
-- | Parse scope from ligo output file generated by
-- ```
-- ligo get-scope contract --format=json --with-types
-- ```
-- and return a hashmap of scope name and our internal scope declarations.
parseScopedDecls
:: FilePath
-> IO (HM.HashMap Text ScopedDecl)
parseScopedDecls contractPath = do
scopes <- parseLigoScopes contractPath
return $ fmap toScopedDecl scopes
-- | Parse scope from ligo output file generated by
-- ```
-- ligo get-scope contract --format=json --with-types
-- ```
-- and return a hashmap of scope name and its values.
parseLigoScopes
:: FilePath
-> IO (HM.HashMap Text LigoScope)
parseLigoScopes contractPath =
let interpret :: Value -> HM.HashMap Text LigoScope
interpret scopes =
scopes
^?! key "definitions" -- TODO: may error here
. key "variables"
^@.. members
. (_JSON :: Prism' Value LigoScope)
^. to HM.fromList
in do
output <- C8.readFile contractPath
case eitherDecodeStrict' @Value . encodeUtf8 . pack . C8.unpack $ output of
Left err -> throwM $ ScopeParseError (pack err)
Right scopes -> return $ interpret scopes
-- | Extract types from a ligo scope resolution file generated by -- | Extract types from a ligo scope resolution file generated by
-- ``` -- ```
-- ligo get-scope contract --format=json --with-types -- ligo get-scope contract --format=json --with-types
-- ``` -- ```
parseLigoTypesFor parseLigoTypesFor
:: FilePath :: FilePath -- ^ Ligo output file path
-> Text -> Text -- ^ Declaration name
-> IO [(Text, LigoTypeFull)] -> IO [(Text, LigoTypeFull)]
parseLigoTypesFor contractPath name = do parseLigoTypesFor contractPath name = do
output <- C8.readFile contractPath output <- C8.readFile contractPath
@ -136,13 +184,15 @@ parseLigoTypesFor contractPath name = do
Nothing -> throwM $ VariableExtractError scopes Nothing -> throwM $ VariableExtractError scopes
Just variables' -> return $ extractLigoTypesFrom name variables' Just variables' -> return $ extractLigoTypesFrom name variables'
-- | Get scopes from ligo compiler and extract a list of types associated with some specific variable. ----------------------------------------------------------------------------
getLigoTypesFor -- Execute ligo binary itself
-- | Get raw aeson value for ligo scopes.
getLigoScopesRaw
:: LigoClientEnv :: LigoClientEnv
-> FilePath -> FilePath
-> Text -> IO Value
-> IO [(Text, LigoTypeFull)] getLigoScopesRaw env contractPath = do
getLigoTypesFor env contractPath name = do
output <- callLigo env ["get-scope", contractPath, "--format=json", "--with-types"] output <- callLigo env ["get-scope", contractPath, "--format=json", "--with-types"]
case eitherDecodeStrict' @Value . encodeUtf8 . pack $ output of case eitherDecodeStrict' @Value . encodeUtf8 . pack $ output of
Left err -> throwM $ ScopeParseError (pack err) Left err -> throwM $ ScopeParseError (pack err)
@ -150,9 +200,33 @@ getLigoTypesFor env contractPath name = do
let variables = scopes ^? key "definitions" . key "variables" let variables = scopes ^? key "definitions" . key "variables"
case variables of case variables of
Nothing -> throwM $ VariableExtractError scopes Nothing -> throwM $ VariableExtractError scopes
Just variables' -> return $ extractLigoTypesFrom name variables' Just variables' -> return variables'
-- | Extract a list of types in scopes from aeson @Value@ for some specific declaration. -- | Extract scoped decls from ligo call.
getLigoScopedDecls
:: LigoClientEnv
-> FilePath
-> IO (HM.HashMap Text ScopedDecl)
getLigoScopedDecls env contractPath = do
scopes <- getLigoScopesRaw env contractPath
let interpreted =
scopes
^@.. members
. (_JSON :: Prism' Value LigoScope)
^. to HM.fromList
return $ toScopedDecl <$> interpreted
-- | Get scopes from ligo compiler and extract a list of types associated with some specific variable.
getLigoTypesFor
:: LigoClientEnv -- ^ Client environment
-> FilePath -- ^ Contract path
-> Text -- ^ Declaration name
-> IO (HM.HashMap Text LigoTypeFull)
getLigoTypesFor env contractPath name = do
scopes <- getLigoScopesRaw env contractPath
return $ HM.fromList $ extractLigoTypesFrom name scopes
-- | Extract a list of types in scopes from aeson @Value@ for some specific declaration under "name" field.
extractLigoTypesFrom :: Text -> Value -> [(Text, LigoTypeFull)] extractLigoTypesFrom :: Text -> Value -> [(Text, LigoTypeFull)]
extractLigoTypesFrom name context = extractLigoTypesFrom name context =
let current = let current =

View File

@ -1,13 +1,16 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric, RecordWildCards #-}
{-# LANGUAGE RecordWildCards #-}
-- | The definition of type as is represented in ligo JSON output -- | The definition of type as is represented in ligo JSON output
module Cli.Json module Cli.Json
( LigoTypeFull (..) ( LigoScope (..)
, LigoTypeFull (..)
, LigoTypeContent (..) , LigoTypeContent (..)
, LigoTypeContentInner (..) , LigoTypeContentInner (..)
, LigoRecordField (..) , LigoRecordField (..)
, LigoLocation (..) , LigoRange (..)
, convertLigoRange
, toScopedDecl
, prepareField
) )
where where
@ -15,28 +18,41 @@ import Data.Aeson
import Data.Char (isUpper, toLower) import Data.Char (isUpper, toLower)
import Data.Foldable (asum, toList) import Data.Foldable (asum, toList)
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics import GHC.Generics
import AST.Scope
import Range
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- Types -- Types
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
data LigoScope = LigoScope
{ _lsName :: Text
, _lsRange :: LigoRange
, _lsBodyRange :: LigoRange
, _lsT :: LigoTypeFull
, _lsReferences :: Value
}
deriving stock (Generic, Show)
-- | Whole ligo type. -- | Whole ligo type.
-- ``` -- ```
-- { "t" : LigoTypeFull } -- { "t" : LigoTypeFull }
-- ``` -- ```
data LigoTypeFull = LigoTypeFull data LigoTypeFull = LigoTypeFull
{ -- | Location of the definition. { -- | Location of the definition.
ltLocation :: LigoLocation _ltLocation :: LigoRange
, -- | *Some* meta constructors (e.g. `Some`). , -- | *Some* meta constructors (e.g. `Some`).
ltTypeMeta :: Value _ltTypeMeta :: Value
, -- | We parse it by a chunks of 2, each odd element of array is a name for , -- | We parse it by a chunks of 2, each odd element of array is a name for
-- even element which is `LigoTypeContentInner`. -- even element which is `LigoTypeContentInner`.
-- ``` -- ```
-- { "type_content": [ <name>, LigoTypeContentInner ] } -- { "type_content": [ <name>, LigoTypeContentInner ] }
-- ``` -- ```
ltTypeContent :: [LigoTypeContent] _ltTypeContent :: [LigoTypeContent]
} }
deriving stock (Generic, Show) deriving stock (Generic, Show)
@ -45,8 +61,8 @@ data LigoTypeFull = LigoTypeFull
-- { "type_content": LigoTypeContent } -- { "type_content": LigoTypeContent }
-- ``` -- ```
data LigoTypeContent = LigoTypeContent data LigoTypeContent = LigoTypeContent
{ ltcName :: Text { _ltcName :: Text
, ltcContentInner :: LigoTypeContentInner , _ltcContentInner :: LigoTypeContentInner
} }
deriving stock (Generic, Show) deriving stock (Generic, Show)
@ -57,15 +73,15 @@ data LigoTypeContent = LigoTypeContent
data LigoTypeContentInner data LigoTypeContentInner
= -- | Type call represented by the list of arguments and its constructor. = -- | Type call represented by the list of arguments and its constructor.
LTCConst LTCConst
{ ltciArguments :: [Text] { _ltciArguments :: [Text]
, ltciTypeConst :: Value , _ltciTypeConst :: Value
} }
| -- | Record type. | -- | Record type.
LTCRecord (HM.HashMap Text LigoRecordField) -- TODO: continue LTCRecord (HM.HashMap Text LigoRecordField) -- TODO: continue
| -- | Arrow type, note that the order of its arguments is reversed. | -- | Arrow type, note that the order of its arguments is reversed.
LTCArrow -- "type2" -> "type1" LTCArrow -- "type2" -> "type1"
{ ltcType2 :: LigoTypeFull { _ltciType2 :: LigoTypeFull
, ltcType1 :: LigoTypeFull , _ltciType1 :: LigoTypeFull
} }
deriving stock (Generic, Show) deriving stock (Generic, Show)
@ -76,27 +92,27 @@ data LigoTypeContentInner
data LigoRecordField = LigoRecordField data LigoRecordField = LigoRecordField
{ -- | Declaration position (don't ask me I too don't know what actual { -- | Declaration position (don't ask me I too don't know what actual
-- position is this since from all the example it's somewhat always 0). -- position is this since from all the example it's somewhat always 0).
lrfDeclPos :: Int _lrfDeclPos :: Int
, -- | How the value is represented in michelson, currently ignored , -- | How the value is represented in michelson, currently ignored
-- during parsing. -- during parsing.
lrfMichelsonAnnotation :: Value _lrfMichelsonAnnotation :: Value
, -- | The type itself. , -- | The type itself.
lrfAssociatedType :: LigoTypeFull _lrfAssociatedType :: LigoTypeFull
} }
deriving stock (Generic, Show) deriving stock (Generic, Show)
-- | Location of type definition. -- | Location of type definition.
-- ``` -- ```
-- { "location": LigoLocation } -- { "location": LigoRange }
-- ``` -- ```
data LigoLocation data LigoRange
= Virtual Text = Virtual Text
| LigoLocation | LigoRange
{ llFile :: FilePath { _lrFile :: FilePath
, llFromRow :: Int , _lrFromRow :: Int
, llFromCol :: Int , _lrFromCol :: Int
, llToRow :: Int , _lrToRow :: Int
, llToCol :: Int , _lrToCol :: Int
} }
deriving stock (Generic, Show) deriving stock (Generic, Show)
@ -104,20 +120,26 @@ data LigoLocation
-- Instances -- Instances
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
instance FromJSON LigoScope where
parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = prepareField 2}
instance ToJSON LigoScope where
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 2}
-- TODO: We trust ligo compiler output for printing even number -- TODO: We trust ligo compiler output for printing even number
-- of array elements. -- of array elements.
instance FromJSON LigoTypeFull where instance FromJSON LigoTypeFull where
parseJSON = withObject "type_full" $ \o -> do parseJSON = withObject "type_full" $ \o -> do
ltLocation <- o .: "location" _ltLocation <- o .: "location"
type_content <- o .: "type_content" type_content <- o .: "type_content"
ltTypeContent <- _ltTypeContent <-
withArray "type_content" (mapM proceed . group 2 . toList) type_content withArray "type_content" (mapM proceed . group 2 . toList) type_content
ltTypeMeta <- o .: "type_meta" _ltTypeMeta <- o .: "type_meta"
return $ LigoTypeFull {..} return $ LigoTypeFull {..}
where where
proceed [name, value] = do proceed [name, value] = do
ltcName <- parseJSON @Text name _ltcName <- parseJSON @Text name
ltcContentInner <- parseJSON @LigoTypeContentInner value _ltcContentInner <- parseJSON @LigoTypeContentInner value
return $ LigoTypeContent {..} return $ LigoTypeContent {..}
proceed _ = error "number of type content elements is not even and cannot be grouped" proceed _ = error "number of type content elements is not even and cannot be grouped"
@ -147,20 +169,20 @@ instance FromJSON LigoRecordField where
instance ToJSON LigoRecordField where instance ToJSON LigoRecordField where
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 3} toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 3}
instance FromJSON LigoLocation where instance FromJSON LigoRange where
parseJSON = withObject "location" $ \o -> parseJSON = withObject "location" $ \o ->
asum asum
[ Virtual <$> o .: "virtual" [ Virtual <$> o .: "virtual"
, do , do
llFile <- o .: "file" _lrFile <- o .: "file"
llFromRow <- o .: "from_row" _lrFromRow <- o .: "from_row"
llFromCol <- o .: "from_col" _lrFromCol <- o .: "from_col"
llToRow <- o .: "to_row" _lrToRow <- o .: "to_row"
llToCol <- o .: "to_col" _lrToCol <- o .: "to_col"
return $ LigoLocation {..} return $ LigoRange {..}
] ]
instance ToJSON LigoLocation where instance ToJSON LigoRange where
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 2} toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 2}
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
@ -169,10 +191,10 @@ instance ToJSON LigoLocation where
-- | Helper function that converts qualified field to its JSON counterpart. -- | Helper function that converts qualified field to its JSON counterpart.
-- --
-- >>> prepareField 2 "llFooBar" -- >>> prepareField 2 "__llFooBar"
-- "foo_bar" -- "foo_bar"
prepareField :: Int -> String -> String prepareField :: Int -> String -> String
prepareField dropAmount = Prelude.drop (dropAmount + 1) . concatMap process prepareField dropAmount = Prelude.drop (dropAmount + 2) . concatMap process
where where
process c process c
| isUpper c = "_" <> [toLower c] | isUpper c = "_" <> [toLower c]
@ -187,3 +209,34 @@ group _ [] = []
group n l group n l
| n > 0 = (take n l) : (group n (drop n l)) | n > 0 = (take n l) : (group n (drop n l))
| otherwise = error "Negative or zero n" | otherwise = error "Negative or zero n"
-- | Converts ligo ranges to our internal ones.
-- >>> convertLigoRange (LigoRange "test.ligo" 1 2 3 4)
-- [test.ligo:1:2-3:4]
convertLigoRange :: LigoRange -> Maybe Range
convertLigoRange (Virtual _) = Nothing
convertLigoRange LigoRange {..} =
Just
Range
{ rStart = (_lrFromRow, _lrFromCol, 0)
, rFinish = (_lrToRow, _lrToCol, 0)
, rFile = _lrFile
}
-- | Converts ligo scope to our internal one.
-- TODO: convert `LigoTypeFull` to `LIGO ()`
toScopedDecl :: LigoScope -> ScopedDecl
toScopedDecl
LigoScope
{ _lsName = _sdName
, _lsRange = (fromMaybe (error "no origin range") . convertLigoRange -> _sdOrigin)
, _lsBodyRange = (convertLigoRange -> _sdBody)
} =
ScopedDecl
{ _sdName
, _sdOrigin
, _sdBody
, _sdType = Nothing
, _sdRefs = []
, _sdDoc = []
}