packages feed

cpsa-3.6.6: src/CPSA/GoalSat/Main.hs

-- Identify unsatisfied skeletons

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module Main (main) where

import Numeric
import System.IO
import System.Console.GetOpt
import CPSA.Lib.SExpr
import CPSA.Lib.Printer (pp)
import CPSA.Lib.Entry

-- Runtime parameters

data Params = Params
    { file :: Maybe FilePath,   -- Nothing specifies standard output
      margin :: Int }           -- Output line length
    deriving Show

defaultIndent :: Int
defaultIndent = optIndent defaultOptions

defaultMargin :: Int
defaultMargin = optMargin defaultOptions

main :: IO ()
main =
    do
      (p, params) <- start options interp
      h <- outputHandle $ file params
      go (writeCpsaLn (pp (margin params) defaultIndent) h) p
      hClose h

writeCpsaLn :: (SExpr a -> String) -> Handle -> SExpr a -> IO ()
writeCpsaLn printer h sexpr =
    hPutStrLn h $ printer sexpr

-- Command line option flags
data Flag
    = Help                      -- Help
    | Info                      -- Version information
    | Margin String             -- Output line length
    | Output String             -- Output file name
      deriving Show

options :: [OptDescr Flag]
options =
    [ Option ['o'] ["output"]   (ReqArg Output "FILE") "output FILE",
      Option ['m'] ["margin"]   (ReqArg Margin "INT")
      ("set output margin (default " ++ show defaultMargin ++ ")"),
      Option ['h'] ["help"]     (NoArg Help)           "show help message",
      Option ['v'] ["version"]  (NoArg Info)           "show version number" ]

-- Interpret option flags
interp :: [Flag] -> IO Params
interp flags =
    loop flags (Params { file = Nothing, -- By default, no output file
                         margin = defaultMargin })
    where
      loop [] params = return params
      loop (Output name : flags) params
          | file params == Nothing =
              loop flags $ params { file = Just name }
      loop (Margin value : flags) params =
          case readDec value of
            [(margin, "")] ->
                loop flags $ params { margin = margin }
            _ ->
                do
                  msg <- usage options ["Bad value for margin\n"]
                  abort msg
      loop (Info : _) _ =
          success cpsaVersion
      loop (Help : _) _ =
          do                    -- Show help then exit with success
            msg <- usage options []
            success msg
      loop _ _ =
           do                   -- Show help then exit with failure
             msg <- usage options ["Bad option combination\n"]
             abort msg

-- Entry point for finding unsatisfied skeletons
go :: (SExpr () -> IO ()) -> PosHandle -> IO ()
go pp p =
  pov pp p

-- Look for a point of view that contains a goal.
pov :: (SExpr () -> IO ()) -> PosHandle -> IO ()
pov pp p =
  do
    x <- gentlyReadSExpr p
    case x of
      Nothing ->
        return ()
      Just sexpr ->
        case sexpr of
          L _ (S _ "defskeleton" : _ : xs) ->
            case label xs of    -- Get laber
              Nothing ->
                abort "Skeleton without a label"
              Just lab ->       -- See if it has goals
                if hasKey "goals" xs then
                  skel pp p [lab] -- Has a goal
                else
                  prot pp p     -- Else skip this problem
          _ ->
            pov pp p

-- Get the label number of a skeleton
label :: [SExpr a] -> Maybe Int
label xs =
  case assoc "label" xs of
    [N _ lab] ->
      Just lab
    _ ->
      Nothing

-- Search for next protocol definition
prot :: (SExpr () -> IO ()) -> PosHandle -> IO ()
prot pp p =
  do
    x <- gentlyReadSExpr p
    case x of
      Nothing ->
        return ()
      Just sexpr ->
        case sexpr of
          L _ (S _ "defprotocol" : _ ) ->
            pov pp p
          _ ->
            prot pp p

-- Look for skeleton that are unsatisfied.
skel :: (SExpr () -> IO ()) -> PosHandle -> [Int] -> IO ()
skel pp p sats =
  do
    x <- gentlyReadSExpr p
    case x of
      Nothing ->
        return ()
      Just sexpr ->
        case sexpr of
          L _ (S _ "defskeleton" : _ : xs) ->
            if unsat xs then    -- If skeleton is unsatisfied
              case label xs of
                Nothing ->
                  abort "Skeleton without a label"
                Just lab ->     -- Then add its label to the list
                  skel pp p (lab : sats)
            else                -- Skeleton okay
              skel pp p sats
          _ ->                  -- Not a skeleton
            do                  -- Print results
              pp (L () (map (N ()) (reverse sats)))
              pov pp p          -- And then search for POV

-- Is skeleton unsatisfied?
unsat :: [SExpr a] -> Bool
unsat xs =                      -- Satisfies entry has at least
  hasKey "no" $ assoc "satisfies" xs -- one no

-- Lookup value in alist, appending values with the same key
assoc :: String -> [SExpr a] -> [SExpr a]
assoc key alist =
    concat [ rest | L _ (S _ head : rest) <- alist, key == head ]

-- See if alist has a key
hasKey :: String -> [SExpr a] -> Bool
hasKey key alist =
    any f alist
    where
      f (L _ (S _ head : _)) = head == key
      f _ = False