
Problem: We want to integrate our lsp client with ligo binary to be able to typecheck declarations as well as extract their scopes. Solution: Implement a simple client that calls ligo and some helpers functions to extract scopes from its output.
190 lines
5.9 KiB
Haskell
190 lines
5.9 KiB
Haskell
{-# LANGUAGE DeriveGeneric #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
-- | The definition of type as is represented in ligo JSON output
|
|
module Cli.Json
|
|
( LigoTypeFull (..)
|
|
, LigoTypeContent (..)
|
|
, LigoTypeContentInner (..)
|
|
, LigoRecordField (..)
|
|
, LigoLocation (..)
|
|
)
|
|
where
|
|
|
|
import Data.Aeson
|
|
import Data.Char (isUpper, toLower)
|
|
import Data.Foldable (asum, toList)
|
|
import qualified Data.HashMap.Strict as HM
|
|
import Data.Text (Text)
|
|
import GHC.Generics
|
|
|
|
----------------------------------------------------------------------------
|
|
-- Types
|
|
----------------------------------------------------------------------------
|
|
|
|
-- | Whole ligo type.
|
|
-- ```
|
|
-- { "t" : LigoTypeFull }
|
|
-- ```
|
|
data LigoTypeFull = LigoTypeFull
|
|
{ -- | Location of the definition.
|
|
ltLocation :: LigoLocation
|
|
, -- | *Some* meta constructors (e.g. `Some`).
|
|
ltTypeMeta :: Value
|
|
, -- | We parse it by a chunks of 2, each odd element of array is a name for
|
|
-- even element which is `LigoTypeContentInner`.
|
|
-- ```
|
|
-- { "type_content": [ <name>, LigoTypeContentInner ] }
|
|
-- ```
|
|
ltTypeContent :: [LigoTypeContent]
|
|
}
|
|
deriving stock (Generic, Show)
|
|
|
|
-- | A pair in "type_content" array `[name, content]`.
|
|
-- ```
|
|
-- { "type_content": LigoTypeContent }
|
|
-- ```
|
|
data LigoTypeContent = LigoTypeContent
|
|
{ ltcName :: Text
|
|
, ltcContentInner :: LigoTypeContentInner
|
|
}
|
|
deriving stock (Generic, Show)
|
|
|
|
-- | Inner object representing type content that depends on `name` in `LigoTypeContent`.
|
|
-- ```
|
|
-- { "type_content": [ <type>, LigoTypeContentInner ] }
|
|
-- ```
|
|
data LigoTypeContentInner
|
|
= -- | Type call represented by the list of arguments and its constructor.
|
|
LTCConst
|
|
{ ltciArguments :: [Text]
|
|
, ltciTypeConst :: Value
|
|
}
|
|
| -- | Record type.
|
|
LTCRecord (HM.HashMap Text LigoRecordField) -- TODO: continue
|
|
| -- | Arrow type, note that the order of its arguments is reversed.
|
|
LTCArrow -- "type2" -> "type1"
|
|
{ ltcType2 :: LigoTypeFull
|
|
, ltcType1 :: LigoTypeFull
|
|
}
|
|
deriving stock (Generic, Show)
|
|
|
|
-- | Record field type value.
|
|
-- ```
|
|
-- { "type_content": ["T_record", { "key": LigoRecordField } ] }
|
|
-- ```
|
|
data LigoRecordField = LigoRecordField
|
|
{ -- | 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).
|
|
lrfDeclPos :: Int
|
|
, -- | How the value is represented in michelson, currently ignored
|
|
-- during parsing.
|
|
lrfMichelsonAnnotation :: Value
|
|
, -- | The type itself.
|
|
lrfAssociatedType :: LigoTypeFull
|
|
}
|
|
deriving stock (Generic, Show)
|
|
|
|
-- | Location of type definition.
|
|
-- ```
|
|
-- { "location": LigoLocation }
|
|
-- ```
|
|
data LigoLocation
|
|
= Virtual Text
|
|
| LigoLocation
|
|
{ llFile :: FilePath
|
|
, llFromRow :: Int
|
|
, llFromCol :: Int
|
|
, llToRow :: Int
|
|
, llToCol :: Int
|
|
}
|
|
deriving stock (Generic, Show)
|
|
|
|
----------------------------------------------------------------------------
|
|
-- Instances
|
|
----------------------------------------------------------------------------
|
|
|
|
-- TODO: We trust ligo compiler output for printing even number
|
|
-- of array elements.
|
|
instance FromJSON LigoTypeFull where
|
|
parseJSON = withObject "type_full" $ \o -> do
|
|
ltLocation <- o .: "location"
|
|
type_content <- o .: "type_content"
|
|
ltTypeContent <-
|
|
withArray "type_content" (mapM proceed . group 2 . toList) type_content
|
|
ltTypeMeta <- o .: "type_meta"
|
|
return $ LigoTypeFull {..}
|
|
where
|
|
proceed [name, value] = do
|
|
ltcName <- parseJSON @Text name
|
|
ltcContentInner <- parseJSON @LigoTypeContentInner value
|
|
return $ LigoTypeContent {..}
|
|
proceed _ = error "number of type content elements is not even and cannot be grouped"
|
|
|
|
instance ToJSON LigoTypeFull where
|
|
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 2}
|
|
|
|
instance FromJSON LigoTypeContent where
|
|
parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = prepareField 3}
|
|
|
|
instance ToJSON LigoTypeContent where
|
|
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 3}
|
|
|
|
instance FromJSON LigoTypeContentInner where
|
|
parseJSON = withObject "type_content" $ \o ->
|
|
asum
|
|
[ LTCConst <$> o .: "arguments" <*> o .: "type_constant"
|
|
, LTCRecord <$> sequence (parseJSON @LigoRecordField <$> o)
|
|
, LTCArrow <$> o .: "type2" <*> o .: "type1"
|
|
]
|
|
|
|
instance ToJSON LigoTypeContentInner where
|
|
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 3}
|
|
|
|
instance FromJSON LigoRecordField where
|
|
parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = prepareField 3}
|
|
|
|
instance ToJSON LigoRecordField where
|
|
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 3}
|
|
|
|
instance FromJSON LigoLocation where
|
|
parseJSON = withObject "location" $ \o ->
|
|
asum
|
|
[ Virtual <$> o .: "virtual"
|
|
, do
|
|
llFile <- o .: "file"
|
|
llFromRow <- o .: "from_row"
|
|
llFromCol <- o .: "from_col"
|
|
llToRow <- o .: "to_row"
|
|
llToCol <- o .: "to_col"
|
|
return $ LigoLocation {..}
|
|
]
|
|
|
|
instance ToJSON LigoLocation where
|
|
toJSON = genericToJSON defaultOptions {fieldLabelModifier = prepareField 2}
|
|
|
|
----------------------------------------------------------------------------
|
|
-- Helpers
|
|
----------------------------------------------------------------------------
|
|
|
|
-- | Helper function that converts qualified field to its JSON counterpart.
|
|
--
|
|
-- >>> prepareField 2 "llFooBar"
|
|
-- "foo_bar"
|
|
prepareField :: Int -> String -> String
|
|
prepareField dropAmount = Prelude.drop (dropAmount + 1) . concatMap process
|
|
where
|
|
process c
|
|
| isUpper c = "_" <> [toLower c]
|
|
| otherwise = [c]
|
|
|
|
-- | Splits an array onto chunks of n elements, throws error otherwise.
|
|
--
|
|
-- >>> group 2 [1, 2, 3, 4]
|
|
-- [[1,2],[3,4]]
|
|
group :: Int -> [a] -> [[a]]
|
|
group _ [] = []
|
|
group n l
|
|
| n > 0 = (take n l) : (group n (drop n l))
|
|
| otherwise = error "Negative or zero n"
|