packages feed

ogma-core-1.0.9: src/Language/Trans/FRETComponentSpec2Copilot.hs

-- Copyright 2020 United States Government as represented by the Administrator
-- of the National Aeronautics and Space Administration. All Rights Reserved.
--
-- Disclaimers
--
-- No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY
-- OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
-- LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
-- SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-- PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE
-- SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF
-- PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN
-- ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR
-- RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR
-- ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER,
-- GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING
-- THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES
-- IT "AS IS."
--
-- Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
-- THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS
-- ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN
-- ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE,
-- INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S
-- USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE
-- UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY
-- PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY
-- FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS
-- AGREEMENT.
--
{-# LANGUAGE OverloadedStrings #-}

-- | Transform a FRET Component Specification into a Copilot specification.
--
-- Normally, this module would be implemented as a conversion between ASTs, but
-- we want to add comments to the generated code, which are not representable
-- in the abstract syntax tree.
module Language.Trans.FRETComponentSpec2Copilot where

-- External imports
import Data.List  ( intersect, union )
import Data.Maybe ( fromMaybe )

-- External imports: auxiliary
import Data.String.Extra ( sanitizeLCIdentifier, sanitizeUCIdentifier )

-- Internal imports: language ASTs, transformers
import qualified Language.CoCoSpec.ParCoCoSpec   as CoCoSpec ( myLexer,
                                                               pBoolSpec )
import           Language.FRETComponentSpec.AST  as FRET
import qualified Language.Trans.CoCoSpec2Copilot as CoCoSpec ( boolSpec2Copilot )
import           Language.Trans.SMV2Copilot      as SMV ( boolSpec2Copilot )

-- | Options used to customize the conversion of FRET Component Specifications
-- to Copilot code.
data FRETComponentSpec2CopilotOptions = FRETComponentSpec2CopilotOptions
  { fretCS2CopilotUseCoCoSpec :: Bool
  , fretCS2CopilotIntType     :: String
  , fretCS2CopilotRealType    :: String
  , fretCS2CopilotFilename    :: String
  }

-- | Transform a FRET TL specification into a Copilot specification.
--
-- This function may fail with a 'Left' value if the resulting Copilot
-- specification would contain name clashes or other errors.
fretComponentSpec2Copilot :: FRETComponentSpec2CopilotOptions
                          -> FRETComponentSpec
                          -> Either String String
fretComponentSpec2Copilot prefs parseResult =
  fretComponentSpec2Copilot' prefs =<< fret2CopilotAnalyze parseResult

-- | For a given FRET file, return the corresponding Copilot file, or an error
-- message if such file cannot be generated.
--
-- PRE: there are no name clashes between the variables and names used in the
-- FRET specification and any definitions in Haskell's Prelude or in Copilot.
fretComponentSpec2Copilot' :: FRETComponentSpec2CopilotOptions
                           -> FRETComponentSpec
                           -> Either String String
fretComponentSpec2Copilot' prefs fretComponentSpec =
    unlines . concat <$> sequence
      [ pure imports
      , pure externs
      , internals
      , reqs
      , pure clock
      , pure ftp
      , pure pre
      , pure tpre
      , pure spec
      , pure main'
      ]

  where

    -- Import header block
    imports :: [String]
    imports =
      [ "import           Copilot.Compile.C99"
      , "import           Copilot.Language          hiding (prop)"
      , "import           Copilot.Language.Prelude"
      , "import           Copilot.Library.LTL       (next)"
      , "import           Copilot.Library.MTL       hiding (since,"
        ++ " alwaysBeen, trigger)"
      , "import           Copilot.Library.PTLTL     (since, previous,"
        ++ " alwaysBeen)"
      , "import qualified Copilot.Library.PTLTL     as PTLTL"
      , "import qualified Copilot.Library.MTL       as MTL"
      , "import           Language.Copilot          (reify)"
      , "import           Prelude                   hiding ((&&), (||), (++),"
        ++ " (<=), (>=), (<), (>), (==), (/=), not)"
      , ""
      ]

    -- Extern streams
    externs = concatMap externVarToDecl
                        (FRET.fretExternalVariables fretComponentSpec)
      where
        externVarToDecl i = [ FRET.fretExternalVariableName i
                                ++ " :: Stream "
                                ++ "("
                                ++ fretTypeToCopilotType
                                     prefs
                                     (FRET.fretExternalVariableType i)
                                ++ ")"

                            , FRET.fretExternalVariableName i
                                ++ " = "
                                ++ "extern"
                                ++ " "
                                ++ show (FRET.fretExternalVariableName i)
                                ++ " "
                                ++ "Nothing"
                            , ""
                            ]

    -- Internal stream definitions
    internals = concat
             <$> mapM internalVarToDecl
                      (FRET.fretInternalVariables fretComponentSpec)
      where
        internalVarToDecl i = fmap (\implem ->
                                [ FRET.fretInternalVariableName i
                                    ++ " :: Stream "
                                    ++ "("
                                    ++ fretTypeToCopilotType
                                         prefs
                                         (FRET.fretInternalVariableType i)
                                    ++ ")"

                                , FRET.fretInternalVariableName i
                                    ++ " = "
                                    ++ implem

                                , ""
                                ]) implementation
          where
            implementation = if null (FRET.fretInternalVariableCopilot i)
                               then CoCoSpec.boolSpec2Copilot
                                      <$> CoCoSpec.pBoolSpec
                                            ( CoCoSpec.myLexer
                                            $ FRET.fretInternalVariableLustre i
                                            )
                               else pure (FRET.fretInternalVariableCopilot i)

    -- Encoding of requirements as boolean streams
    reqs :: Either String [String]
    reqs = concat <$> mapM reqToDecl (FRET.fretRequirements fretComponentSpec)
      where
        reqToDecl i = sequence
                        [ pure reqComment, pure reqSignature, reqBody, pure "" ]
          where
            -- Definition comment, which includes the requirement for
            -- traceability purposes.
            reqComment = "-- | "  ++ FRET.fretRequirementName i    ++ "\n" ++
                         "--   @"                                  ++ "\n" ++
                         "--   "  ++ FRET.fretRequirementFretish i ++ "\n" ++
                         "--   @"

            -- Definition type signature.
            reqSignature = FRET.fretRequirementName i
                             ++ " :: " ++ "Stream" ++ " " ++ "Bool"

            -- Definition implementation, either in SMV or in CoCoSpec
            reqBody = if fretCS2CopilotUseCoCoSpec prefs
                        then reqBodyCoCo
                        else reqBodyPT

            reqBodyPT = fmap (\e -> FRET.fretRequirementName i ++ " = "
                                      ++ SMV.boolSpec2Copilot e
                             )
                             (fromMaybe (Left $ "No requirement for " ++ show i)
                                        (FRET.fretRequirementPTExpanded i))

            reqBodyCoCo = fmap
                            (\e -> FRET.fretRequirementName i ++ " = "
                                     ++ CoCoSpec.boolSpec2Copilot e
                            )
                            (fromMaybe (Left $ "No requirement for " ++ show i)
                                       (FRET.fretRequirementCoCoSpec i))

    -- Auxiliary streams: clock
    clock :: [String]
    clock = [ ""
            , "-- | Clock that increases in one-unit steps."
            , "clock :: Stream Int64"
            , "clock = [0] ++ (clock + 1)"
            , ""
            ]

    -- Auxiliary streams: first time point
    ftp :: [String]
    ftp = [ ""
          , "-- | First Time Point"
          , "ftp :: Stream Bool"
          , "ftp = [True] ++ false"
          , ""
          ]

    -- Auxiliary streams: pre
    pre = [ ""
          , "pre :: Stream Bool -> Stream Bool"
          , "pre = ([False] ++)"
          ]

    -- Auxiliary streams: tpre
    tpre = [ ""
           , "tpre :: Stream Bool -> Stream Bool"
           , "tpre = ([True] ++)"
           ]

    -- Main specification
    spec :: [String]
    spec = [ ""
           , "-- | Complete specification. Calls the C function void "
             ++ " handler(); when"
           , "-- the property is violated."
           , "spec :: Spec"
           , "spec = do"
           ]
           ++ triggers
           ++ [ "" ]
      where
        triggers :: [String]
        triggers = fmap reqTrigger (FRET.fretRequirements fretComponentSpec)

        reqTrigger :: FRETRequirement -> String
        reqTrigger r = "  trigger " ++ show handlerName ++ " (not "
                       ++ propName ++ ") " ++ "[]"
          where
            handlerName = "handler" ++ FRET.fretRequirementName r
            propName    = FRET.fretRequirementName r

    -- Main program that compiles specification to C in two files (code and
    -- header).
    main' :: [String]
    main' = [ ""
            , "main :: IO ()"
            , "main = reify spec >>= compile \""
                 ++ fretCS2CopilotFilename prefs ++ "\""
            ]

-- | Return the corresponding type in Copilot matching a given FRET type.
fretTypeToCopilotType :: FRETComponentSpec2CopilotOptions -> String -> String
fretTypeToCopilotType _options "bool"    = "Bool"
fretTypeToCopilotType options  "int"     = fretCS2CopilotIntType options
fretTypeToCopilotType options  "integer" = fretCS2CopilotIntType options
fretTypeToCopilotType options  "real"    = fretCS2CopilotRealType options
fretTypeToCopilotType _options "string"  = "String"
fretTypeToCopilotType _options x         = x

-- | Analyze a FRET-Copilot file and determine if there will be any name
-- clashes after the conversion to Copilot.
--
-- This function does not compare against Haskell's prelude or Copilot's
-- modules. It simply makes simple conversions to comply with Copilot/Haskell's
-- grammar (e.g., variable/function names start with lowercase) and determines
-- if the conversion would make two definitions in the given specification
-- produce name clashes between them.
fret2CopilotAnalyze :: FRETComponentSpec -> Either String FRETComponentSpec
fret2CopilotAnalyze fretComponentSpec
    | not (null evnClash)
    = Left $ "Name clash detected: " ++ show evnClash

    | not (null ivnClash)
    = Left $ "Name clash detected: " ++ show ivnClash

    | not (null reqClash)
    = Left $ "Name clash detected: " ++ show reqClash

    | otherwise
    = Right $ foldr applySubstitution fretComponentSpec nameSubstitutions

  where

    -- Sets containing name clashes
    ivnClash = internalVariableNames'
                 `intersect` (externalVariableNames' `union` requirementNames')

    evnClash = externalVariableNames'
                 `intersect` (internalVariableNames' `union` requirementNames')

    reqClash = requirementNames'
                 `intersect` (internalVariableNames'
                                `union` externalVariableNames')

    -- Names used.
    internalVariableNames' = map snd internalVariableMap
    externalVariableNames' = map snd externalVariableMap
    requirementNames'      = map snd requirementNameMap

    -- Map from a variable name to its desired identifier in the code
    -- generated.
    internalVariableMap =
      map (\x -> (x, sanitizeLCIdentifier x)) internalVariableNames

    externalVariableMap =
      map (\x -> (x, sanitizeLCIdentifier x)) externalVariableNames

    requirementNameMap =
      map (\x -> (x, "prop" ++ sanitizeUCIdentifier x)) requirementNames

    nameSubstitutions = internalVariableMap
                     ++ externalVariableMap
                     ++ requirementNameMap

    -- Variable/requirement names used in the component spec.
    internalVariableNames = map fretInternalVariableName
                          $ fretInternalVariables fretComponentSpec

    externalVariableNames = map fretExternalVariableName
                          $ fretExternalVariables fretComponentSpec

    requirementNames = map fretRequirementName
                     $ fretRequirements fretComponentSpec