packages feed

cil-0.0.1: GenCIL.hs

-- | Parses OCaml cil_types.mli file and generates an equivalent CIL.hs file and a supporting Frama-C plugin (-dumpcil).
module Main (main) where

import Data.Char
import Data.List hiding (group)
import System.Process
import Text.ParserCombinators.Poly.Plain
import Text.Printf

main :: IO ()
main = do
  f <- readFile "cil_types.mli"
  writeFile "cil_types_nocomments.mli" $ decomment f
  let types = parseOCaml f
  system "mkdir -p install-dumpcil-plugin"
  writeFile "install-dumpcil-plugin/Makefile"    $ dumpcilMakefile
  writeFile "install-dumpcil-plugin/dump_cil.ml" $ dumpcilPlugin types
  writeFile "CIL.hs" $ haskellCIL types


-- OCaml type types.

-- Type definitions.
data TypeDef
  = Sum    [(String, [TypeApply])]
  | Record [(String, TypeApply)]
  | Alias  TypeApply
  deriving (Show, Eq)

--- Type definition name with parameters.
data TypeName = TypeName String [String] deriving (Show, Eq)

-- Type references.  Either type applications or tuples.
data TypeApply
  = TypeApply VarPar [TypeApply]
  | TypeApplyGroup TypeApply
  deriving (Show, Eq)

-- Type variables or parameters.
data VarPar = Var String | Par String deriving (Show, Eq)



cap :: String -> String
cap [] = []
cap (a:b) = toUpper a : b

isAlias :: TypeDef -> Bool
isAlias (Alias _) = True
isAlias _         = False

-- Haskell CIL module generation.
haskellCIL :: [(TypeName, TypeDef)] -> String
haskellCIL types = unlines
  [ "-- | A Haskell interface to OCaml's CIL library, via Frama-C, providing both a simplied C AST and the ACSL specification language."
  , "module Language.CIL"
  , "  ( parseC"
  , "  , debugParseC"
  , "  , installPlugin"
  , "  , Exn                        (..)"
  , "  , Position                   (..)"
  , "  , Int64                      (..)"
  , unlines [ printf "  , %-26s %s" (cap name) (if isAlias t then "" else "(..)")  | (TypeName name _, t) <- types ]
  , "  )"
  , "  where"
  , ""
  , "import System.Exit"
  , "import System.Process"
  -- , "import Text.Parse"
  , ""
  , "-- | Parse a C compilation unit (file)."
  , "parseC :: FilePath -> IO File"
  , "parseC file = do"
  , "  (exitCode, out, err) <- readProcessWithExitCode \"frama-c\" [\"-dumpcil\", file] \"\""
  , "  let code = unlines $ tail $ lines out"
  , "  case exitCode of"
  , "    ExitSuccess -> return $ read code"
  , "    ExitFailure _ -> putStrLn err >> exitWith exitCode"
  , ""
  , "-- | Prints output from frama-c -dumpcil."
  , "debugParseC :: FilePath -> IO ()"
  , "debugParseC file = do"
  , "  (exitCode, out, err) <- readProcessWithExitCode \"frama-c\" [\"-dumpcil\", file] \"\""
  , "  putStrLn out"
  , ""
  -- {-
  -- case exitCode of
  --   ExitSuccess -> case runParser parse code of
  --     (Left s,  a)  -> putStrLn ("parse error: " ++ s ++ "\n" ++ code ++ "\n" ++ a) >> exitFailure
  --     (Right f, _) ->  return f
  --   ExitFailure _ -> putStrLn err >> exitWith exitCode
  -- -}
  , " -- | Installs Frama-C '-dumpcil' plugin.  Creates 'install-dumpcil-pluging' directory, deposits a Makefile and dump_cil.ml, then runs 'make' and 'make install'."
  , "installPlugin :: IO ()"
  , "installPlugin = do"
  , "  putStrLn \"creating install-dumpcil-plugin directory for plugin compiling and installation ...\""
  , "  system \"mkdir -p install-dumpcil-plugin\""
  , "  writeFile \"install-dumpcil-plugin/Makefile\" " ++ show dumpcilMakefile
  , "  writeFile \"install-dumpcil-plugin/dump_cil.ml\" " ++ show (dumpcilPlugin types)
  , "  putStrLn \"running 'make' to compile dumpcil plugin ...\""
  , "  system \"cd install-dumpcil-plugin && make\""
  , "  putStrLn \"running 'make install' to install dumpcil plugin ...\""
  , "  system \"cd install-dumpcil-plugin && make install\""
  , "  return ()"
  , ""
  , "data Exn = Exn " ++ derives
  , "data Position = Position FilePath Int Int " ++ derives
  , "data Int64 = Int64 " ++ derives
  , ""
  , unlines [ printf "%s %s %s = %s %s" (if isAlias t then "type" else "data") (cap name) (intercalate " " params) (fType name t) (if isAlias t then "" else derives) | (TypeName name params, t) <- types ] 
  ]
  where
  derives = "deriving (Show, Read, Eq) {-! derive : Parse !-}"
  fType :: String -> TypeDef -> String
  fType name t = case t of
    Sum    constructors -> intercalate " | " [ constructorName name ++ concat [ " " ++ group (fTypeApply t) | t <- args ] | (name, args) <- constructors ]
    Record fields -> printf "%s { %s }" (cap name) $ intercalate ", " [ printf "%s :: %s" field (fTypeApply tr) | (field, tr) <- fields ]
    Alias  tr     -> fTypeApply tr

  fTypeApply :: TypeApply -> String
  fTypeApply a = case a of
    TypeApply a []   -> name a
    TypeApply (Var "list")   [a] -> "[" ++ fTypeApply a ++ "]"
    TypeApply (Var "option") [a] -> "Maybe " ++ group (fTypeApply a)
    TypeApply (Var "ref")    [a] -> fTypeApply a
    TypeApply (Var "tuple") args -> group $ intercalate ", " (map fTypeApply args)
    TypeApply a args -> name a ++ concat [ " (" ++ fTypeApply t ++ ")" | t <- args ]
    TypeApplyGroup a -> fTypeApply a
    where
    name (Var n) = cap n
    name (Par n) = n

constructorName :: String -> String
constructorName a = case a of
  "Block"   -> "Block'"
  "True"    -> "True'"
  "False"   -> "False'"
  "Nothing" -> "Nothing'"
  a         -> cap a


-- Frama-C 'dumpcil' plugin generation.
dumpcilPlugin :: [(TypeName, TypeDef)] -> String
dumpcilPlugin types = unlines
  [ "open Ast"
  , "open Cil_types"
  , "open File"
  , "open Lexing"
  , "open List"
  , "open String"
  , "open Int64"
  , "open Char"
  , ""
  , "let string a = \"\\\"\" ^ a ^ \"\\\"\" (* XXX Doesn't handle '\\' or '\"' chars in string. *)"
  , "let position t = \"Position \\\"\" ^ t.pos_fname ^ \"\\\" \" ^ string_of_int t.pos_lnum ^ \" \" ^ string_of_int (t.pos_cnum - t.pos_bol + 1)"
  , "let bool a = if a then \"True\" else \"False\""
  , "let char = Char.escaped"
  , "let int = string_of_int"
  , "let int64 = Int64.to_string"
  , "let float = string_of_float"
  , ""
  , "let rec " ++ intercalate "\nand " (map fType types)
  , ""
  , "let run () ="
  , "  File.init_from_cmdline ();"
  , "  print_endline (file (Ast.get ()))"
  , ""
  , "module Self ="
  , "  Plugin.Register"
  , "    (struct"
  , "      let name = \"dumpcil\""
  , "      let shortname = \"dumpcil\""
  , "      let descr = \"Dumps CIL and ACSL to stdout to be read by Haskell CIL.\""
  , "    end);;"
  , ""
  , "module Enabled ="
  , "  Self.False"
  , "    (struct"
  , "      let option_name = \"-dumpcil\""
  , "      let descr = \"Dumps CIL and ACSL to stdout to be read by Haskell CIL.\""
  , "    end);;"
  , ""
  , "let () = Db.Main.extend (fun () -> if Enabled.get () then run ())"
  ]
  where
  fType :: (TypeName, TypeDef) -> String
  fType (TypeName name args, m) = name ++ concatMap (" " ++) args ++ " m = " ++ case m of  -- Parametric types are passed formating functions for each of the parameters.
    Sum constructors -> function (map constructor constructors) ++ " m"
    Record fields -> "\"" ++ cap name ++ " { " ++ intercalate ", " (map field fields) ++ " }\""
    Alias m -> fTypeApply m ++ " m"

  constructor :: (String, [TypeApply]) -> (String, String)
  constructor (name, [])   = (name, show $ cap name)
  constructor (name, [m])  = (name ++ " m1", show (constructorName name) ++ " ^ \" \" ^ " ++ fTypeApply m ++ " m1")
  constructor (name, args) = (name ++ " " ++ group (intercalate ", " [ "m" ++ show i | i <- [1 .. length args] ]), show (constructorName name) ++ concat [ " ^ \" \" ^ " ++ metaGroup (fTypeApply m ++ " m" ++ show i) | (m, i) <- zip args [1..] ])

  field :: (String, TypeApply) -> String
  field (name, t) = name ++ " = " ++ "\" ^ " ++ fTypeApply t ++ " m." ++ name ++ " ^ \""

  -- Returns an OCaml function :: Data -> String
  fTypeApply :: TypeApply -> String
  fTypeApply m = case m of
    TypeApply (Var "exn")    [] -> "(fun _ -> \"Exn\")"
    TypeApply m [] -> name m  -- Vars are top level functions, Params should be functions passed in and thus in scope.
    TypeApply (Var "list")   [m] -> function [("m", "\"[ \" ^ concat \", \" (map " ++ group (fTypeApply m) ++ " m) ^ \" ]\"")]
    TypeApply (Var "option") [m] -> function [("None", show "Nothing"), ("Some m", show "Just " ++ " ^ " ++ metaGroup (fTypeApply m ++ " m"))]
    TypeApply (Var "ref")    [m] -> function [("m", metaGroup (fTypeApply m ++ " (!m)"))]
    TypeApply (Var "tuple") args -> function [(group $ intercalate ", " [ "m" ++ show i | i <- [1 .. length args] ], metaGroup $ intercalate " ^ \", \" ^ " [ metaGroup (fTypeApply m ++ " m" ++ show i) | (m, i) <- zip args [1..] ])]
    TypeApply m args -> function [("m", metaGroup (name m ++ concat [ " " ++ function [("m", metaGroup (fTypeApply arg ++ " m"))] | arg <- args ] ++ " m"))]
    TypeApplyGroup a -> fTypeApply a
    where
    name (Var n) = n
    name (Par n) = n

  function :: [(String, String)] -> String
  function matches = group $ "function " ++ intercalate " | " [ a ++ " -> " ++ b | (a, b) <- matches ]

group :: String -> String
group a = "(" ++ a ++ ")"

metaGroup :: String -> String
metaGroup a = group $ "\"(\" ^ " ++ a ++ " ^ \")\""


-- | Makefile used to install the dumpcil plugin.
dumpcilMakefile :: String
dumpcilMakefile = unlines
  [ "FRAMAC_SHARE  :=$(shell frama-c.byte -print-path)"
  , "FRAMAC_LIBDIR :=$(shell frama-c.byte -print-libpath)"
  , "PLUGIN_NAME = Dumpcil"
  , "PLUGIN_CMO  = dump_cil"
  , "include $(FRAMAC_SHARE)/Makefile.dynamic"
  ]



-- Lexing.

data Token
  = Type
  | Of
  | Eq
  | Pipe
  | Colon
  | SemiColon
  | Star
  | Comma
  | ParenLeft
  | ParenRight
  | BraceLeft
  | BraceRight
  | Constructor String
  | Variable    String
  | Parameter   String
  deriving (Show, Eq)

lexer :: String -> [Token]
lexer = map t . filter (/= "mutable") . filter (not . null) . concatMap split . words . decomment
  where
  split :: String -> [String]
  split a = case break isSym a of
    (word, []) -> [word]
    (word, (a:b)) -> [word, [a]] ++ split b
  isSym = flip elem "=|;:*,(){}"
  t :: String -> Token
  t a = case a of
    "type" -> Type
    "and"  -> Type
    "of"   -> Of
    "="    -> Eq
    "|"    -> Pipe
    ":"    -> Colon
    ";"    -> SemiColon
    "*"    -> Star
    ","    -> Comma
    "("    -> ParenLeft
    ")"    -> ParenRight
    "{"    -> BraceLeft
    "}"    -> BraceRight
    '\'':a -> Parameter a
    a | elem '.' a -> t $ tail $ dropWhile (/= '.') a
    a:b    | isUpper a -> Constructor (a : b)
           | otherwise -> Variable    (a : b)
    a      -> error $ "unexpected string: " ++ a

decomment :: String -> String
decomment = decomment' 0
  where
  decomment' :: Int -> String -> String
  decomment' n "" | n == 0    = ""
                  | otherwise = error "reached end of file without closing comment"
  decomment' n ('(':'*':a) = "  " ++ decomment' (n + 1) a
  decomment' n ('*':')':a) | n > 0  = "  " ++ decomment' (n - 1) a
                           | otherwise = error "unexpected closing comment"
  decomment' n (a:b) = (if n > 0 && a /= '\n' then ' ' else a) : decomment' n b







-- Parsing.

type OCaml a = Parser Token a

parseOCaml :: String -> [(TypeName, TypeDef)]
parseOCaml a = case runParser (many typeDef `discard` eof) $ lexer a of
  (Left msg, remaining) -> error $ msg ++ "\nremaining tokens: " ++ show (take 30 $ remaining) ++ " ..."
  (Right a, []) -> a
  (Right _, remaining) -> error $ "parsed, but with remaining tokens: " ++ show remaining

tok :: Token -> OCaml ()
tok a = satisfy (== a) >> return ()

parameter :: OCaml String
parameter = do
  a <- satisfy (\ a -> case a of { Parameter _ -> True; _ -> False })
  case a of
    Parameter s -> return s
    _ -> undefined

constructor :: OCaml String
constructor = do
  a <- satisfy (\ a -> case a of { Constructor _ -> True; _ -> False })
  case a of
    Constructor s -> return s
    _ -> undefined

variable :: OCaml String
variable = do
  a <- satisfy (\ a -> case a of { Variable _ -> True; _ -> False })
  case a of
    Variable s -> return s
    _ -> undefined

typeDef :: OCaml (TypeName, TypeDef)
typeDef = do { tok Type; n <- typeName; tok Eq; e <- typeExpr; return (n, e) }

typeName :: OCaml TypeName
typeName = oneOf
  [ do { tok ParenLeft; a <- parameter; b <- many1 (tok Comma >> parameter); tok ParenRight; n <- variable; return $ TypeName n $ a : b }
  , do { p <- parameter; n <- variable; return $ TypeName n [p] }
  , do { n <- variable; return $ TypeName n [] }
  ]

varPars :: OCaml [VarPar]
varPars = do { a <- varPar; b <- many varPar;  return $ a : b }
  where
  varPar :: OCaml VarPar
  varPar = oneOf [variable >>= return . Var, parameter >>= return . Par]

typeApply :: OCaml TypeApply
typeApply = oneOf
  [ do { tok ParenLeft; a <- varPars; b <- many1 (tok Comma >> varPars); tok ParenRight; c <- varPars; return $ TypeApplyGroup $ apply (map (apply []) (a : b)) c }
  , do { tok ParenLeft; a <- typeApply; tok ParenRight; b <- varPars; return $ TypeApplyGroup $ apply [a] b }
  , do { tok ParenLeft; a <- typeApply; tok ParenRight; return $ TypeApplyGroup a }
  -- XXX Need to prevent nested tuples.
  , do { a <- varPars; b <- many1 (tok Star >> typeApply); return $ TypeApply (Var "tuple") $ flattenTuples $ apply [] a : b }
  , do { a <- varPars; return $ apply [] a }
  ]
  where
  apply :: [TypeApply] -> [VarPar] -> TypeApply
  apply _ [] = error "typeApply: no type to apply to"
  apply args [a] = TypeApply a args
  apply args (a:b) = apply [(apply args [a])] b

  flattenTuples :: [TypeApply] -> [TypeApply]
  flattenTuples = concatMap flattenTuple

  flattenTuple :: TypeApply -> [TypeApply]
  flattenTuple (TypeApply (Var "tuple") args) = args
  flattenTuple a = [a]

typeExpr :: OCaml TypeDef
typeExpr = oneOf
  [ recordType >>= return . Record
  , sumType    >>= return . Sum
  , typeApply  >>= return . Alias
  ]

recordType :: OCaml [(String, TypeApply)]
recordType = do { tok BraceLeft; f  <- recordField; fs <- many (tok SemiColon >> recordField); optional $ tok SemiColon; tok BraceRight; return $ f : fs }

recordField :: OCaml (String, TypeApply)
recordField = do { n <- variable; tok Colon; t <- typeApply; return (n, t) }

sumType :: OCaml [(String, [TypeApply])]
sumType = do { optional (tok Pipe); a <- sumConstructor; b <- many (tok Pipe >> sumConstructor); return $ a : b }
 
sumConstructor :: OCaml (String, [TypeApply])
sumConstructor = oneOf
  [ do { n <- constructor; tok Of; a <- typeApply; return (n, detuple a) }
  , do { n <- constructor; return (n, []) }
  ]
  where
  detuple :: TypeApply -> [TypeApply]
  detuple (TypeApply (Var "tuple") args) = args
  detuple a = [a]