Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ogma-cli: Provide ability to preprocess properties via external command. Refs #197. #198

Merged
merged 8 commits into from
Jan 15, 2025
3 changes: 2 additions & 1 deletion ogma-cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Revision history for ogma-cli

## [1.X.Y] - 2025-01-10
## [1.X.Y] - 2025-01-14

* Update contribution guidelines (#161).
* Provide ability to customize template in fprime command (#185).
* Provide ability to customize template in standalone command (#189).
* Add repository information to cabal package (#148).
* Add version bounds to all dependencies (#119).
* Introduce new diagram command (#194).
* Provide ability to preprocess properties via external command (#197).

## [1.5.0] - 2024-11-21

Expand Down
14 changes: 14 additions & 0 deletions ogma-cli/src/CLI/CommandStandalone.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ data CommandOpts = CommandOpts
, standalonePropFormat :: String
, standaloneTypes :: [String]
, standaloneTarget :: String
, standalonePropVia :: Maybe String
}

-- | Transform an input specification into a Copilot specification.
Expand All @@ -79,6 +80,7 @@ command c = standalone (standaloneFileName c) internalCommandOpts
, Command.Standalone.standalonePropFormat = standalonePropFormat c
, Command.Standalone.standaloneTypeMapping = types
, Command.Standalone.standaloneFilename = standaloneTarget c
, Command.Standalone.standalonePropVia = standalonePropVia c
}

types :: [(String, String)]
Expand Down Expand Up @@ -150,6 +152,13 @@ commandOptsParser = CommandOpts
<> showDefault
<> value "monitor"
)
<*> optional
( strOption
( long "parse-prop-via"
<> metavar "COMMAND"
<> help strStandalonePropViaDesc
)
)

-- | Target dir flag description.
strStandaloneTargetDirDesc :: String
Expand Down Expand Up @@ -180,6 +189,11 @@ strStandaloneTargetDesc :: String
strStandaloneTargetDesc =
"Filename prefix for monitoring files in target language"

-- | External command to pre-process individual properties.
strStandalonePropViaDesc :: String
strStandalonePropViaDesc =
"Command to pre-process individual properties"

-- * Error codes

-- | Encoding of reasons why the command can fail.
Expand Down
3 changes: 2 additions & 1 deletion ogma-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Revision history for ogma-core

## [1.X.Y] - 2025-01-10
## [1.X.Y] - 2025-01-14

* Replace queueSize with QUEUE_SIZE in FPP file (#186).
* Use template expansion system to generate F' monitoring component (#185).
* Use template expansion system to generate standalone Copilot monitor (#189).
* Add repository information to cabal package (#148).
* Add version bounds to all dependencies (#119).
* Add command to transform state diagrams into monitors (#194).
* Extend standalone command to use external process to parse properties (#197).

## [1.5.0] - 2024-11-21

Expand Down
1 change: 1 addition & 0 deletions ogma-core/ogma-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ library
, graphviz >= 2999.20 && < 2999.21
, megaparsec >= 8.0.0 && < 9.10
, mtl >= 2.2.2 && < 2.4
, process >= 1.6 && < 1.7
, text >= 1.2.3.1 && < 2.1

, ogma-extra >= 1.5.0 && < 1.6
Expand Down
17 changes: 10 additions & 7 deletions ogma-core/src/Command/FPrimeApp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ module Command.FPrimeApp

-- External imports
import qualified Control.Exception as E
import Control.Monad.Except ( ExceptT, liftEither, liftIO, runExceptT,
throwError )
import Control.Monad.Except ( ExceptT(..), liftEither, liftIO,
runExceptT, throwError )
import Data.Aeson ( eitherDecode, object, (.=) )
import Data.Char ( toUpper )
import Data.List ( find, intercalate, nub, sort )
Expand Down Expand Up @@ -167,12 +167,15 @@ parseOptionalFRETCS Nothing = return Nothing
parseOptionalFRETCS (Just fp) = do
-- Throws an exception if the file cannot be read.
content <- liftIO $ B.safeReadFile fp
let fretCS :: Either String (Spec String)
fretCS = parseJSONSpec return fretFormat =<< eitherDecode =<< content

case fretCS of
Left e -> throwError $ cannotOpenFRETFile fp e
Right cs -> return $ Just cs
fretCS <- case eitherDecode =<< content of
Left e -> ExceptT $ return $ Left $ cannotOpenFRETFile fp e
Right v -> ExceptT $ do
p <- parseJSONSpec (return . return) fretFormat v
case p of
Left e -> return $ Left $ cannotOpenFRETFile fp e
Right r -> return $ Right r
return $ Just fretCS

-- | Process a variable selection file, if available, and return the variable
-- names.
Expand Down
17 changes: 10 additions & 7 deletions ogma-core/src/Command/ROSApp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ module Command.ROSApp

-- External imports
import qualified Control.Exception as E
import Control.Monad.Except (ExceptT, liftEither, liftIO, runExceptT,
throwError)
import Control.Monad.Except (ExceptT(..), liftEither, liftIO,
runExceptT, throwError)
import Data.Aeson (eitherDecode, object, (.=))
import Data.List (find, intersperse)
import Data.Maybe (fromMaybe)
Expand Down Expand Up @@ -176,12 +176,15 @@ parseOptionalFRETCS Nothing = return Nothing
parseOptionalFRETCS (Just fp) = do
-- Throws an exception if the file cannot be read.
content <- liftIO $ B.safeReadFile fp
let fretCS :: Either String (Spec String)
fretCS = parseJSONSpec return fretFormat =<< eitherDecode =<< content

case fretCS of
Left e -> throwError $ cannotOpenFRETFile fp e
Right cs -> return $ Just cs
fretCS <- case eitherDecode =<< content of
Left e -> ExceptT $ return $ Left $ cannotOpenFRETFile fp e
Right v -> ExceptT $ do
p <- parseJSONSpec (return . return) fretFormat v
case p of
Left e -> return $ Left $ cannotOpenFRETFile fp e
Right r -> return $ Right r
return $ Just fretCS

-- | Process a variable selection file, if available, and return the variable
-- names.
Expand Down
26 changes: 25 additions & 1 deletion ogma-core/src/Command/Standalone.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- Copyright 2020 United States Government as represented by the Administrator
-- of the National Aeronautics and Space Administration. All Rights Reserved.
--
Expand Down Expand Up @@ -45,6 +46,7 @@ import Data.ByteString.Lazy (fromStrict)
import Data.Foldable (for_)
import Data.List (nub, (\\))
import Data.Maybe (fromMaybe)
import System.Process (readProcess)
import System.FilePath ((</>))
import Data.Text.Lazy (pack)

Expand Down Expand Up @@ -144,13 +146,17 @@ standalone' fp options (ExprPair parse replace print ids) = do

format <- read <$> readFile formatFile

let wrapper = wrapVia (standalonePropVia options) parse

-- All of the following operations use Either to return error messages. The
-- use of the monadic bind to pass arguments from one function to the next
-- will cause the program to stop at the earliest error.
content <- B.safeReadFile fp
res <- case content of
Left s -> return $ Left s
Right b -> return $ parseJSONSpec parse format =<< eitherDecode b
Right b -> do case eitherDecode b of
Left e -> return $ Left e
Right v -> parseJSONSpec wrapper format v

-- Complement the specification with any missing/implicit definitions
let res' = fmap (addMissingIdentifiers ids) res
Expand All @@ -159,6 +165,23 @@ standalone' fp options (ExprPair parse replace print ids) = do

return copilot

-- | Parse a property using an auxiliary program to first translate it, if
-- available.
--
-- If a program is given, it is first called on the property, and then the
-- result is parsed with the parser passed as an argument. If a program is not
-- given, then the parser is applied to the given string.
wrapVia :: Maybe String -- ^ Auxiliary program to translate the
-- property.
-> (String -> Either String a) -- ^ Parser used on the result.
-> String -- ^ Property to parse.
-> IO (Either String a)
wrapVia Nothing parse s = return (parse s)
wrapVia (Just f) parse s =
E.handle (\(e :: IOException) -> return $ Left $ show e) $ do
out <- readProcess f [] s
return $ parse out

-- | Options used to customize the conversion of specifications to Copilot
-- code.
data StandaloneOptions = StandaloneOptions
Expand All @@ -168,6 +191,7 @@ data StandaloneOptions = StandaloneOptions
, standalonePropFormat :: String
, standaloneTypeMapping :: [(String, String)]
, standaloneFilename :: String
, standalonePropVia :: Maybe String
}

-- * Error codes
Expand Down
2 changes: 2 additions & 0 deletions ogma-core/tests/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ testFretComponentSpec2Copilot file success = do
, standaloneFilename = "fret"
, standaloneTargetDir = targetDir
, standaloneTemplateDir = Nothing
, standalonePropVia = Nothing
}
result <- standalone file opts

Expand Down Expand Up @@ -145,6 +146,7 @@ testFretReqsDBCoCoSpec2Copilot file success = do
, standaloneFilename = "fret"
, standaloneTargetDir = targetDir
, standaloneTemplateDir = Nothing
, standalonePropVia = Nothing
}
result <- standalone file opts

Expand Down
3 changes: 2 additions & 1 deletion ogma-language-jsonspec/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Revision history for ogma-language-jsonspec

## [1.X.Y] - 2024-12-24
## [1.X.Y] - 2025-01-14

* Add repository information to cabal package (#148).
* Add version bounds to all dependencies (#119).
* Extend JSON spec parser to use an IO action to parse properties (#197).

## [1.5.0] - 2024-11-21

Expand Down
1 change: 1 addition & 0 deletions ogma-language-jsonspec/ogma-language-jsonspec.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ library
, jsonpath >= 0.3 && < 0.4
, text >= 1.2.3.1 && < 2.1
, megaparsec >= 8.0.0 && < 9.10
, mtl >= 2.2.2 && < 2.4
, bytestring >= 0.10.8.2 && < 0.13

, ogma-spec >= 1.5.0 && < 1.6
Expand Down
23 changes: 14 additions & 9 deletions ogma-language-jsonspec/src/Language/JSONSpec/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
module Language.JSONSpec.Parser where

-- External imports
import Control.Monad.Except (ExceptT (..), runExceptT)
import Data.Aeson (FromJSON (..), Value (..), decode, (.:))
import Data.Aeson.Key (toString)
import qualified Data.Aeson.KeyMap as M
Expand Down Expand Up @@ -107,9 +108,9 @@ parseJSONFormat jsonFormat = do
, jfiRequirementExpr = jfi12
}

parseJSONSpec :: (String -> Either String a) -> JSONFormat -> Value -> Either String (Spec a)
parseJSONSpec parseExpr jsonFormat value = do
jsonFormatInternal <- parseJSONFormat jsonFormat
parseJSONSpec :: (String -> IO (Either String a)) -> JSONFormat -> Value -> IO (Either String (Spec a))
parseJSONSpec parseExpr jsonFormat value = runExceptT $ do
jsonFormatInternal <- except $ parseJSONFormat jsonFormat

let values :: [Value]
values = maybe [] (`executeJSONPath` value) (jfiInternalVars jsonFormatInternal)
Expand All @@ -131,7 +132,7 @@ parseJSONSpec parseExpr jsonFormat value = do
, internalVariableExpr = varExpr
}

internalVariableDefs <- mapM internalVarDef values
internalVariableDefs <- except $ mapM internalVarDef values

let values :: [Value]
values = maybe [] (`executeJSONPath` value) (jfiExternalVars jsonFormatInternal)
Expand All @@ -150,22 +151,22 @@ parseJSONSpec parseExpr jsonFormat value = do
, externalVariableType = varType
}

externalVariableDefs <- mapM externalVarDef values
externalVariableDefs <- except $ mapM externalVarDef values

let values :: [Value]
values = executeJSONPath (jfiRequirements jsonFormatInternal) value

-- requirementDef :: Value -> Either String (Requirement a)
requirementDef value = do
let msg = "Requirement name"
reqId <- valueToString msg =<< (listToEither msg (executeJSONPath (jfiRequirementId jsonFormatInternal) value))
reqId <- except $ valueToString msg =<< (listToEither msg (executeJSONPath (jfiRequirementId jsonFormatInternal) value))

let msg = "Requirement expression"
reqExpr <- valueToString msg =<< (listToEither msg (executeJSONPath (jfiRequirementExpr jsonFormatInternal) value))
reqExpr' <- parseExpr reqExpr
reqExpr <- except $ valueToString msg =<< (listToEither msg (executeJSONPath (jfiRequirementExpr jsonFormatInternal) value))
reqExpr' <- ExceptT $ parseExpr reqExpr

let msg = "Requirement description"
reqDesc <- maybe (Right "") (\e -> valueToString msg =<< (listToEither msg (executeJSONPath e value))) (jfiRequirementDesc jsonFormatInternal)
reqDesc <- except $ maybe (Right "") (\e -> valueToString msg =<< (listToEither msg (executeJSONPath e value))) (jfiRequirementDesc jsonFormatInternal)

return $ Requirement
{ requirementName = reqId
Expand Down Expand Up @@ -198,3 +199,7 @@ showErrorsM :: Show a => Maybe (Either a b) -> Either String (Maybe b)
showErrorsM Nothing = Right Nothing
showErrorsM (Just (Left s)) = Left (show s)
showErrorsM (Just (Right x)) = Right (Just x)

-- | Wrap an 'Either' value in an @ExceptT m@ monad.
except :: Monad m => Either e a -> ExceptT e m a
except = ExceptT . return
Loading