packages feed

asil-1.2: src/GenInstrLib.ag

-- Generates symbol information for the instrumentation library
--
-- Note: we ignore the difference between static fields and instance fields.

MODULE {GenInstrLib} {genInstrLib} {}

INCLUDE "SymViewAst.ag"

imports
{
import Data.Word
import qualified Data.Set as Set
import Data.Set(Set)
import qualified Data.Map as Map
import Data.Map(Map)
import ProgInfo
import PrettyUtil
import SymInfo
import SymView
import Data.Monoid
import Data.Maybe
}


WRAPPER FileV

{
genInstrLib :: String -> SymInfo -> [SymbolTables] -> Doc
genInstrLib nm gEnv tbls = res where
  res = output_Syn_FileV syn
  syn = wrap_FileV sem inh
  inh = Inh_FileV { gEnv_Inh_FileV = gEnv, modNm_Inh_FileV = nm }
  sem = sem_FileV fil
  fil = tablesView tbls
}

ATTR FileV [ modNm : String | | ]
ATTR FileV TablesV TableV ClassesV ClassV MbSuperV SuperV ItfsV ItfV TraitsV TraitV MethodsV MethodV SigV
  [ gEnv : SymInfo | | output USE {$+$} {empty} : Doc ]

SEM FileV | File
  lhs.output = vert [ text "{-# LANGUAGE EmptyDataDecls, MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances, OverlappingInstances, FlexibleInstances #-}"
                    , text "module" <+> text @lhs.modNm <+> text "where"
                    , text "import Instr"
                    , text "import InstrBaseLib"
                    , (vert $ map (\m -> text "import" <+> text m) $ Set.toList $ allModules @lhs.gEnv)
                    , @loc.fieldsOutput
                    , @tables.output ]

-- Discover which types and fields are defined
ATTR TablesV TableV ClassesV ClassV [ | | gathTypes USE {`mappend`} {mempty} : {Set String} ]
ATTR TablesV TableV ClassesV ClassV TraitsV TraitV [ | | gathFields USE {`mappend`} {mempty} : {Set String} ]

SEM ClassV | Class  +gathTypes  = maybe id Set.insert @nm.mbStrUni
SEM TraitV
  | Field  +gathFields = maybe id Set.insert @nm.mbStrUni
  | Method +gathFields = maybe id Set.insert @nm.mbStrUni

ATTR TablesV TableV ClassesV ClassV MbSuperV SuperV TypeV ItfsV ItfV TraitsV TraitV MethodsV MethodV SigV ParamsV ParamV [ allTypes : {Set String} | | ]
ATTR TablesV TableV ClassesV ClassV TraitsV TraitV [ allFields : {Set String} | | ]

SEM FileV | File
  tables.allTypes  = @tables.gathTypes `mappend` (allTypes @lhs.gEnv)
  tables.allFields = @tables.gathFields `mappend` (allFields @lhs.gEnv)

ATTR TypeV [ | | tpKnown : Bool ]

SEM TypeV | Type
  loc.tpKnown = not @isNull && maybe False (`Set.member` @lhs.allTypes) @nm.mbStrUni

-- Unicode string of a name
ATTR NmV TypeV [ | | mbStrUni : {Maybe String} ]
SEM NmV
  | Qual   loc.noPrefix = null @ns.str
           loc.strUni   = if @loc.noPrefix then @nm.str else @ns.str ++ "˳" ++ @nm.str  -- special unicode separator
           lhs.mbStrUni = Just @loc.strUni
  | Quals  lhs.mbStrUni = Nothing
  | Other  lhs.mbStrUni = Nothing

ATTR NsV StrV [ | | str : String ]
SEM StrV | Str  lhs.str = @val

ATTR MbStrV [ | | mbStrUni : {Maybe String} ]
SEM MbStrV
  | Nothing  lhs.mbStrUni = Nothing
  | Just     lhs.mbStrUni = Just @just.str


--
-- Generate classes for new fields
--

SEM FileV | File
  loc.newFields    = Set.toList $ Set.difference (Set.map unqualField @tables.gathFields) (Set.map unqualField $ allFields @lhs.gEnv)
  loc.fieldsOutput = vert $ map genFieldClass @loc.newFields

{
-- | Strips the qualifier from the qualified/encoded field name
unqualField :: String -> String
unqualField [] = error "unqualField: field name empty"
unqualField nm = case break (== '˳') nm of
  (rem, []) -> last $ words $ map repl rem  -- take the last of the normal dot chars
  (_, rem)  -> tail rem  -- has the special dot char
  where repl '.' = ' '
        repl c   = c

-- | Assumes that the name is already unqualified
genFieldClass :: String -> Doc
genFieldClass nm = vert
  [ text "class K'" <> text str <+> text "a b | a -> b where"
  , nest 2 $ text "k'" <> text str <+> text ":: Key a b"
  ]
  where str = encodeNmHaskell nm

encodeNmHaskell :: String -> String
encodeNmHaskell = map repl where
  repl '.' = '\''
  repl '˳' = '\''
  repl '-' = '_'
  repl ':' = '_'
  repl '/' = '_'
  repl c = c
}


--
-- Generate data types types for newly introduced classes
--

SEM ClassV | Class
  +output = maybe id (\str -> (genClassData str $+$)) @nm.mbStrUni

{
genClassData :: String -> Doc
genClassData nm = vert
  [ text "data T'" <> text str
  , text "t'" <> text str <+> text ":: Type" <+> text "T'" <> text str
  , text "t'" <> text str <+> text "= TypeObject" <+> text (show nm)
  ] where str = encodeNmHaskell nm
}


--
-- Generate instances for inheritance relation among classes
--

ATTR MbSuperV SuperV ItfsV ItfV TraitsV TraitV [ classNm : String | | ]
SEM ClassV | Class
  loc.classNm = maybe "{-# ClassV_Class: improper name #-}" id @nm.mbStrUni

SEM SuperV | Super
  loc.super = maybe "{-# SuperV_Super: improper name #-}" id @tp.mbStrUni
  +output = if @tp.tpKnown
            then (genSuper @loc.super @lhs.classNm $+$)
            else id

SEM ItfV | Itf
  loc.super = maybe "{-# ItfV_Itf: improper name #-}" id @tp.mbStrUni
  +output = if @tp.tpKnown
            then (genSuper @loc.super @lhs.classNm $+$)
            else id

{
genSuper :: String -> String -> Doc
genSuper super child = text "instance IsSuper" <+> text "T'" <> text superStr <+> text "T'" <> text childStr where
  superStr = encodeNmHaskell super
  childStr = encodeNmHaskell child
}


--
-- Generate keys for fields (fully qualified)
--

ATTR TypeV [ | | strUni : String ]
SEM TypeV | Type
  lhs.strUni = if @loc.tpKnown
               then maybe "{-# TypeV_Type: expecting a type name #}" id @nm.mbStrUni
               else "any"

ATTR TypeV [ | | strAnnot : String ]
SEM TypeV | Type
  lhs.strAnnot = maybe "" id @nm.mbStrUni

SEM TraitV | Field
  lhs.output = maybe empty (\nm -> let strType = encodeNmHaskell @tp.strUni
                                       tp  = text "T'" <> text strType
                                       val = text "Key" <+> text (show nm)
                                   in vert [ text "-- object field" <+> text (show nm) <+> text "::" <+> text (show @tp.strAnnot)
                                           , if nm `Set.member` @lhs.newFields
                                             then text "-- DUPLICATED value field!"
                                             else genTrait @lhs.classNm nm val tp
                                           ]) @nm.mbStrUni


--
-- Generate method signatures
--

SEM TraitV | Method
  lhs.output = maybe empty (\nm -> vert [ text "-- object method" <+> text (show nm)
                                        , if nm `Set.member` @lhs.newFields
                                          then text "-- DUPLICATED method field!"
                                          else genTrait @lhs.classNm nm (text "Key" <+> text (show nm)) @sig.tpSig
                                        ]) @nm.mbStrUni

{
genTrait :: String -> String -> Doc -> Doc -> Doc
genTrait classNm fieldNm val tp = vert
  [ text "k'" <> text strClass <> text "'" <> text strField <+> text ":: Key T'" <> text strClass <+> parens tp
  , text "k'" <> text strClass <> text "'" <> text strField <+> text "=" <+> val
  , text "instance K'" <> text strField' <+> parens (text "T'" <> text strClass) <+> parens tp <+> text "where"
  , nest 2 (text "k'" <> text strField' <+> text "=" <+> text "k'" <> text strClass <> text "'" <> text strField)
  ] where strClass  = encodeNmHaskell classNm
          strField  = encodeNmHaskell fieldNm
          fieldNm'  = unqualField fieldNm
          strField' = encodeNmHaskell fieldNm'
}

SEM MethodV | Method
  lhs.output = maybe empty (\nm -> vert [ text "-- method" <+> text (show nm)
                                        , if nm `Set.member` @lhs.newMethods
                                          then text "-- DUPLICATED method declaration"
                                          else genMethod nm @sig.output @sig.tpSig
                                        ]) @mbNm.mbStrUni

{
genMethod :: String -> Doc -> Doc -> Doc
genMethod methodNm val tp = vert
  [ text "m'" <> text strMethod <+> text "::" <+> tp
  , text "m'" <> text strMethod <+> text "="  <+> val
  ] where strMethod = encodeNmHaskell methodNm
}


ATTR SigV [ | | tpSig : Doc ]  -- 'SigV' has both 'output' and 'tpSig' as pretty docs

SEM SigV | Sig
  lhs.output = text "FunSig"  <+> parens (genSigVal @params.strsUni)  <+> parens (genSigVal [@ret.strUni])
  lhs.tpSig  = text "FunSpec" <+> parens (genSigTp @params.strsUni)   <+> parens (genSigTp [@ret.strUni])

ATTR ParamsV ParamV [ | | strsUni USE {++} {[]} : {[String]} ]

SEM ParamV | Param
  lhs.strsUni = [@tp.strUni]

{
genSigVal :: [String] -> Doc
genSigVal = foldr (\l r -> parens (text "SpecsCons" <+> text "t'" <> text (encodeNmHaskell l) <+> r)) (text "SpecsEmpty")

genSigTp :: [String] -> Doc
genSigTp = foldr (\l r -> parens (text "T'" <> text (encodeNmHaskell l) <+> text "," <+> r)) (text "()")
}


--
-- Compute duplicate fields and methods
-- More symbols for these should not be generated
--

ATTR TablesV TableV ClassesV ClassV TraitsV TraitV [ | newFields : {Set String} | ]
ATTR TablesV TableV ClassesV ClassV MethodsV MethodV [ | newMethods : {Set String} | ]

SEM FileV | File
  tables.newFields  = Set.empty
  tables.newMethods = Set.empty

SEM TraitV | Field Method
  +newFields = maybe id Set.insert @nm.mbStrUni

SEM MethodV | Method
  +newMethods = maybe id Set.insert @mbNm.mbStrUni