{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module FreeC.Environment.ModuleInterface.Decoder ( loadModuleInterface ) where
import Control.Monad ( when )
import Control.Monad.IO.Class ( MonadIO )
import Data.Aeson ( (.!=), (.:), (.:?) )
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import Text.Read ( readMaybe )
import qualified FreeC.Backend.Agda.Syntax as Agda
import qualified FreeC.Backend.Coq.Syntax as Coq
import FreeC.Environment.Entry
import FreeC.Environment.ModuleInterface
import FreeC.Frontend.IR.Parser
import FreeC.IR.Reference ( freeTypeVars )
import FreeC.IR.SrcSpan
import qualified FreeC.IR.Syntax as IR
import FreeC.LiftedIR.Effect
import FreeC.Monad.Reporter
import FreeC.Pretty
import FreeC.Util.Config
moduleInterfaceFileFormatVersion :: Integer
moduleInterfaceFileFormatVersion = 6
parseAesonIR :: Parseable a => String -> Aeson.Parser a
parseAesonIR input = do
let srcFile = mkSrcFile "<config-input>" input
(res, ms) = runReporter (parseIR srcFile)
case res of
Nothing -> Aeson.parseFail (showPretty ms)
Just t -> return t
instance Aeson.FromJSON IR.QName where
parseJSON = Aeson.withText "IR.QName" $ parseAesonIR . Text.unpack
instance Aeson.FromJSON Coq.Qualid where
parseJSON = Aeson.withText "Coq.Qualid" $ return . Coq.bare . Text.unpack
instance Aeson.FromJSON Agda.QName where
parseJSON = Aeson.withText "Agda.QName"
$ return . Agda.qname' . Agda.name . Text.unpack
instance Aeson.FromJSON IR.Type where
parseJSON = Aeson.withText "IR.Type" $ parseAesonIR . Text.unpack
instance Aeson.FromJSON Effect where
parseJSON = Aeson.withText "Effect" $ parseEffect . Text.unpack
where
parseEffect :: String -> Aeson.Parser Effect
parseEffect effectName = case readMaybe effectName of
Just effect -> return effect
Nothing -> Aeson.parseFail ("Unknown effect: " ++ effectName)
instance Aeson.FromJSON ModuleInterface where
parseJSON = Aeson.withObject "ModuleInterface" $ \env -> do
version <- env .: "version"
when (version /= moduleInterfaceFileFormatVersion)
$ Aeson.parseFail
$ "Expected version "
++ show moduleInterfaceFileFormatVersion
++ ", got version "
++ show version
++ ".\n"
++ "Try to recompile the module."
modName <- env .: "module-name"
libName <- env .: "library-name"
exportedTypes <- env .: "exported-types"
exportedValues <- env .: "exported-values"
types <- env .:? "types" .!= Aeson.Array Vector.empty
>>= Aeson.withArray "Data types" (mapM parseConfigType)
typeSyns <- env .:? "type-synonyms" .!= Aeson.Array Vector.empty
>>= Aeson.withArray "Type Synonyms" (mapM parseConfigTypeSyn)
cons <- env .:? "constructors" .!= Aeson.Array Vector.empty
>>= Aeson.withArray "Constructors" (mapM parseConfigCon)
funcs <- env .:? "functions" .!= Aeson.Array Vector.empty
>>= Aeson.withArray "Functions" (mapM parseConfigFunc)
return ModuleInterface
{ interfaceModName = modName
, interfaceLibName = libName
, interfaceAgdaLibName = Agda.name $ Text.unpack $ libName
, interfaceExports = Set.fromList
(map ((,) IR.TypeScope) exportedTypes
++ map ((,) IR.ValueScope) exportedValues)
, interfaceEntries = Set.fromList
(Vector.toList types
++ Vector.toList typeSyns
++ Vector.toList cons
++ Vector.toList funcs)
}
where
parseConfigType :: Aeson.Value -> Aeson.Parser EnvEntry
parseConfigType = Aeson.withObject "Data type" $ \obj -> do
arity <- obj .: "arity"
haskellName <- obj .: "haskell-name"
coqName <- obj .: "coq-name"
agdaName <- obj .: "agda-name"
consNames <- obj .: "cons-names"
return DataEntry { entrySrcSpan = NoSrcSpan
, entryArity = arity
, entryIdent = coqName
, entryAgdaIdent = agdaName
, entryName = haskellName
, entryConsNames = consNames
}
parseConfigTypeSyn :: Aeson.Value -> Aeson.Parser EnvEntry
parseConfigTypeSyn = Aeson.withObject "Type synonym" $ \obj -> do
arity <- obj .: "arity"
typeSyn <- obj .: "haskell-type"
typeArgs <- obj .: "type-arguments"
haskellName <- obj .: "haskell-name"
coqName <- obj .: "coq-name"
agdaName <- obj .: "agda-name"
return TypeSynEntry
{ entrySrcSpan = NoSrcSpan
, entryArity = arity
, entryTypeArgs = typeArgs
, entryTypeSyn = typeSyn
, entryIdent = coqName
, entryAgdaIdent = agdaName
, entryName = haskellName
}
parseConfigCon :: Aeson.Value -> Aeson.Parser EnvEntry
parseConfigCon = Aeson.withObject "Constructor" $ \obj -> do
arity <- obj .: "arity"
haskellName <- obj .: "haskell-name"
haskellType <- obj .: "haskell-type"
coqName <- obj .: "coq-name"
coqSmartName <- obj .: "coq-smart-name"
agdaName <- obj .: "agda-name"
agdaSmartName <- obj .: "agda-smart-name"
let (argTypes, returnType) = IR.splitFuncType haskellType arity
return ConEntry
{ entrySrcSpan = NoSrcSpan
, entryArity = arity
, entryTypeArgs = freeTypeVars returnType
, entryArgTypes = argTypes
, entryReturnType = returnType
, entryIdent = coqName
, entrySmartIdent = coqSmartName
, entryAgdaIdent = agdaName
, entryAgdaSmartIdent = agdaSmartName
, entryName = haskellName
}
parseConfigFunc :: Aeson.Value -> Aeson.Parser EnvEntry
parseConfigFunc = Aeson.withObject "Function" $ \obj -> do
arity <- obj .: "arity"
haskellName <- obj .: "haskell-name"
haskellType <- obj .: "haskell-type"
strictArgs <- obj .: "strict-args"
effects <- obj .: "effects"
freeArgsNeeded <- obj .: "needs-free-args"
effectsEncapsulated <- obj .:? "encapsulates-effects" .!= False
coqName <- obj .: "coq-name"
agdaName <- obj .: "agda-name"
let (argTypes, returnType) = IR.splitFuncType haskellType arity
typeArgs = freeTypeVars haskellType
return FuncEntry
{ entrySrcSpan = NoSrcSpan
, entryArity = arity
, entryTypeArgs = typeArgs
, entryArgTypes = argTypes
, entryStrictArgs = strictArgs
, entryReturnType = returnType
, entryNeedsFreeArgs = freeArgsNeeded
, entryEncapsulatesEffects = effectsEncapsulated
, entryEffects = effects
, entryIdent = coqName
, entryAgdaIdent = agdaName
, entryName = haskellName
}
loadModuleInterface
:: (MonadIO r, MonadReporter r) => FilePath -> r ModuleInterface
loadModuleInterface = loadConfig