1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
--------------------------------------------------------------------------
--- This is the main module to start the executable of the analysis system.
---
--- @author Michael Hanus
--- @version April 2024
--------------------------------------------------------------------------

module CASS.Main ( main ) where

import Data.Char             ( toLower )
import Data.List             ( isPrefixOf, isSuffixOf, sort, init )
import Control.Monad         ( when, unless )
import System.CurryPath      ( stripCurrySuffix )
import System.FilePath       ( (</>), (<.>) )
import System.Process        ( exitWith )
import System.Environment    ( getArgs )
import System.Console.GetOpt

import Analysis.Files     ( deleteAllAnalysisFiles )
import Analysis.Logging   ( debugMessage )
import CASS.Configuration
import CASS.Doc           ( getAnalysisDoc )
import CASS.Options
import CASS.Server        ( analyzeModuleAndPrint, mainServer )
import CASS.Registry      ( registeredAnalysisInfos, registeredAnalysisNames )
import CASS.Worker        ( startWorker )

--- Main function to start the analysis system.
--- With option -s or --server, the server is started on a socket.
--- Otherwise, it is started in batch mode to analyze a single module.
main :: IO ()
main = do
  argv <- getArgs
  let (funopts, args, opterrors) = getOpt Permute options argv
  let opts = foldl (flip id) defaultOptions funopts
  unless (null opterrors)
         (putStr (unlines opterrors) >> putStr usageText >> exitWith 1)
  cconfig <- readRCFile
  when (optHelp opts) (printHelp args >> exitWith 1)
  when (optDelete opts) (deleteFiles args)
  when ((optServer opts && not (null args)) ||
        (not (optServer opts) && length args /= 2))
       (writeErrorAndExit "Illegal arguments (try `-h' for help)")
  when (optWorker opts && length args /= 2)
       (writeErrorAndExit "Illegal arguments (try `-h' for help)")
  let cconfig1 = foldr (uncurry updateProperty) cconfig (optProp opts)
      verb     = optVerb opts
      cconfig2 = if verb >= 0
                   then setDebugLevel verb cconfig1
                   else cconfig1
      dl       = debugLevel cconfig2
  debugMessage dl 1 systemBanner
  if optServer opts
   then mainServer cconfig2
                   (let p = optPort opts in if p == 0 then Nothing else Just p)
   else
     if optWorker opts
       then startWorker cconfig2 (head args) (read (args!!1))
       else do
         let [ananame,mname] = args
         fullananame <- checkAnalysisName ananame
         debugMessage dl 1 $
           "Computing results for analysis `" ++ fullananame ++ "'"
         analyzeModuleAndPrint cconfig2 fullananame (stripCurrySuffix mname)
                               (optAll opts) (optFormat opts) (optReAna opts)
 where
  deleteFiles args = case args of
    [aname] -> do fullaname <- checkAnalysisName aname
                  putStrLn $ "Deleting files for analysis `" ++ fullaname ++ "'"
                  deleteAllAnalysisFiles fullaname
                  exitWith 0
    [] -> writeErrorAndExit "Missing analysis name!"
    _  -> writeErrorAndExit
            "Too many arguments (only analysis name should be given)!"

writeErrorAndExit :: String -> IO _
writeErrorAndExit msg = putStrLn ("ERROR: " ++ msg) >> exitWith 1

-- Checks whether a given analysis name is a unique abbreviation
-- of a registered analysis name and return the registered name.
-- Otherwise, raise an error.
checkAnalysisName :: String -> IO String
checkAnalysisName aname = case matchedNames of
  []       -> writeErrorAndExit $
                "Unknown analysis name `"++ aname ++ "' " ++ tryCmt
  [raname] -> return raname
  (_:_:_)  -> writeErrorAndExit $
                "Analysis name `"++ aname ++ "' not unique " ++ tryCmt ++
                ":\nPossible names are: " ++ unwords matchedNames
 where
  laname        = map toLower aname
  exactMatches  = filter ((== laname) . map toLower)
                         registeredAnalysisNames
  prefixMatches = filter (isPrefixOf laname . map toLower)
                         registeredAnalysisNames
  matchedNames  = if null exactMatches then prefixMatches else exactMatches
  tryCmt        = "(try `-h' for help)"

--------------------------------------------------------------------------
-- Printing help:
printHelp :: [String] -> IO ()
printHelp args =
  if null args
   then putStrLn $ systemBanner ++ "\n" ++ usageText
   else do aname <- checkAnalysisName (head args)
           getAnalysisDoc aname >>=
             maybe (putStrLn $
                      "Sorry, no documentation for analysis `" ++ aname ++ "'")
                   putStrLn

-- Help text
usageText :: String
usageText =
  usageInfo ("Usage: cass <options> <analysis name> <module name>\n" ++
             "   or: cass <options> [-s|--server]\n" ++
             "   or: cass [-w|--worker] <host> <port>\n")
            options ++
  unlines ("" : "Registered analyses names:" :
           "(use option `-h <analysis name>' for more documentation)" :
           "" : map showAnaInfo (sort registeredAnalysisInfos))
 where
  maxName = foldr1 max (map (length . fst) registeredAnalysisInfos) + 1
  showAnaInfo (n,t) = n ++ take (maxName - length n) (repeat ' ') ++ ": " ++ t

--------------------------------------------------------------------------