module CompilerOpts
( Options (..), CppOpts (..), PrepOpts (..), WarnOpts (..), DebugOpts (..)
, CaseMode (..), CymakeMode (..), Verbosity (..), TargetType (..)
, WarnFlag (..), KnownExtension (..), DumpLevel (..), dumpLevel
, defaultOptions, defaultPrepOpts, defaultWarnOpts, defaultDebugOpts
, getCompilerOpts, updateOpts, usage
) where
import Data.List (intercalate, nub)
import Data.Maybe (isJust)
import Data.Char (isDigit)
import qualified Data.Map as Map (Map, empty, insert)
import System.Console.GetOpt
import System.Environment (getArgs, getProgName)
import System.FilePath ( addTrailingPathSeparator, normalise
, splitSearchPath )
import Curry.Files.Filenames (currySubdir)
import Curry.Syntax.Extension
data Options = Options
{ optMode :: CymakeMode
, optVerbosity :: Verbosity
, optForce :: Bool
, optLibraryPaths :: [FilePath]
, optImportPaths :: [FilePath]
, optHtmlDir :: Maybe FilePath
, optUseSubdir :: Bool
, optInterface :: Bool
, optPrepOpts :: PrepOpts
, optWarnOpts :: WarnOpts
, optTargetTypes :: [TargetType]
, optExtensions :: [KnownExtension]
, optDebugOpts :: DebugOpts
, optCaseMode :: CaseMode
, optCppOpts :: CppOpts
} deriving Show
data CppOpts = CppOpts
{ cppRun :: Bool
, cppDefinitions :: Map.Map String Int
} deriving Show
data PrepOpts = PrepOpts
{ ppPreprocess :: Bool
, ppCmd :: String
, ppOpts :: [String]
} deriving Show
data CaseMode
= CaseModeFree
| CaseModeHaskell
| CaseModeProlog
| CaseModeGoedel
deriving (Eq, Show)
data WarnOpts = WarnOpts
{ wnWarn :: Bool
, wnWarnFlags :: [WarnFlag]
, wnWarnAsError :: Bool
} deriving Show
data DebugOpts = DebugOpts
{ dbDumpLevels :: [DumpLevel]
, dbDumpEnv :: Bool
, dbDumpRaw :: Bool
, dbDumpAllBindings :: Bool
, dbDumpSimple :: Bool
} deriving Show
defaultOptions :: Options
defaultOptions = Options
{ optMode = ModeMake
, optVerbosity = VerbStatus
, optForce = False
, optLibraryPaths = []
, optImportPaths = []
, optHtmlDir = Nothing
, optUseSubdir = True
, optInterface = True
, optPrepOpts = defaultPrepOpts
, optWarnOpts = defaultWarnOpts
, optTargetTypes = []
, optExtensions = []
, optDebugOpts = defaultDebugOpts
, optCaseMode = CaseModeFree
, optCppOpts = defaultCppOpts
}
defaultCppOpts :: CppOpts
defaultCppOpts = CppOpts
{ cppRun = False
, cppDefinitions = Map.empty
}
defaultPrepOpts :: PrepOpts
defaultPrepOpts = PrepOpts
{ ppPreprocess = False
, ppCmd = ""
, ppOpts = []
}
defaultWarnOpts :: WarnOpts
defaultWarnOpts = WarnOpts
{ wnWarn = True
, wnWarnFlags = stdWarnFlags
, wnWarnAsError = False
}
defaultDebugOpts :: DebugOpts
defaultDebugOpts = DebugOpts
{ dbDumpLevels = []
, dbDumpEnv = False
, dbDumpRaw = False
, dbDumpAllBindings = False
, dbDumpSimple = False
}
data CymakeMode
= ModeHelp
| ModeVersion
| ModeNumericVersion
| ModeMake
deriving (Eq, Show)
data Verbosity
= VerbQuiet
| VerbStatus
deriving (Eq, Ord, Show)
verbosities :: [(Verbosity, String, String)]
verbosities = [ ( VerbQuiet , "0", "quiet" )
, ( VerbStatus, "1", "status")
]
data TargetType
= Tokens
| Comments
| Parsed
| FlatCurry
| TypedFlatCurry
| TypeAnnotatedFlatCurry
| AbstractCurry
| UntypedAbstractCurry
| Html
| AST
| ShortAST
deriving (Eq, Show)
data WarnFlag
= WarnMultipleImports
| WarnDisjoinedRules
| WarnUnusedGlobalBindings
| WarnUnusedBindings
| WarnNameShadowing
| WarnOverlapping
| WarnIncompletePatterns
| WarnMissingSignatures
| WarnMissingMethods
| WarnOrphanInstances
| WarnIrregularCaseMode
deriving (Eq, Bounded, Enum, Show)
stdWarnFlags :: [WarnFlag]
stdWarnFlags =
[ WarnMultipleImports , WarnDisjoinedRules
, WarnUnusedBindings , WarnNameShadowing , WarnOverlapping
, WarnIncompletePatterns, WarnMissingSignatures, WarnMissingMethods
, WarnIrregularCaseMode
]
warnFlags :: [(WarnFlag, String, String)]
warnFlags =
[ ( WarnMultipleImports , "multiple-imports"
, "multiple imports" )
, ( WarnDisjoinedRules , "disjoined-rules"
, "disjoined function rules" )
, ( WarnUnusedGlobalBindings, "unused-global-bindings"
, "unused bindings" )
, ( WarnUnusedBindings , "unused-bindings"
, "unused bindings" )
, ( WarnNameShadowing , "name-shadowing"
, "name shadowing" )
, ( WarnOverlapping , "overlapping"
, "overlapping function rules" )
, ( WarnIncompletePatterns , "incomplete-patterns"
, "incomplete pattern matching" )
, ( WarnMissingSignatures , "missing-signatures"
, "missing type signatures" )
, ( WarnMissingMethods , "missing-methods"
, "missing method implementations" )
, ( WarnOrphanInstances , "orphan-instances"
, "orphan instances" )
, ( WarnIrregularCaseMode , "irregular-case-mode"
, "irregular case mode")
]
data DumpLevel
= DumpCondCompiled
| DumpParsed
| DumpExtensionChecked
| DumpTypeSyntaxChecked
| DumpKindChecked
| DumpSyntaxChecked
| DumpPrecChecked
| DumpDeriveChecked
| DumpInstanceChecked
| DumpTypeChecked
| DumpExportChecked
| DumpQualified
| DumpDerived
| DumpDesugared
| DumpDictionaries
| DumpNewtypes
| DumpSimplified
| DumpLifted
| DumpTranslated
| DumpCaseCompleted
| DumpTypedFlatCurry
| DumpFlatCurry
deriving (Eq, Bounded, Enum, Show)
dumpLevel :: [(DumpLevel, String, String)]
dumpLevel = [ (DumpCondCompiled , "dump-cond" , "conditional compiling" )
, (DumpParsed , "dump-parse", "parsing" )
, (DumpExtensionChecked , "dump-exc" , "extension checking" )
, (DumpTypeSyntaxChecked, "dump-tsc" , "type syntax checking" )
, (DumpKindChecked , "dump-kc" , "kind checking" )
, (DumpSyntaxChecked , "dump-sc" , "syntax checking" )
, (DumpPrecChecked , "dump-pc" , "precedence checking" )
, (DumpDeriveChecked , "dump-dc" , "derive checking" )
, (DumpInstanceChecked , "dump-inc" , "instance checking" )
, (DumpTypeChecked , "dump-tc" , "type checking" )
, (DumpExportChecked , "dump-ec" , "export checking" )
, (DumpQualified , "dump-qual" , "qualification" )
, (DumpDerived , "dump-deriv", "deriving" )
, (DumpDesugared , "dump-ds" , "desugaring" )
, (DumpDictionaries , "dump-dict" , "dictionary insertion" )
, (DumpNewtypes , "dump-new" , "removing newtype constructors" )
, (DumpLifted , "dump-lift" , "lifting" )
, (DumpSimplified , "dump-simpl", "simplification" )
, (DumpTranslated , "dump-trans", "pattern matching compilation" )
, (DumpCaseCompleted , "dump-cc" , "case completion" )
, (DumpTypedFlatCurry , "dump-tflat", "translation into typed FlatCurry")
, (DumpFlatCurry , "dump-flat" , "translation into FlatCurry" )
]
extensions :: [(KnownExtension, String, String)]
extensions =
[ ( AnonFreeVars , "AnonFreeVars"
, "enable anonymous free variables" )
, ( CPP , "CPP"
, "run C preprocessor" )
, ( ExistentialQuantification, "ExistentialQuantification"
, "enable existentially quantified types" )
, ( FunctionalPatterns , "FunctionalPatterns"
, "enable functional patterns" )
, ( NegativeLiterals , "NegativeLiterals"
, "desugar negated literals as negative literal" )
, ( NoImplicitPrelude , "NoImplicitPrelude"
, "do not implicitly import the Prelude" )
]
type OptErr = (Options, [String])
type OptErrTable opt = [(String, String, opt -> opt)]
onOpts :: (Options -> Options) -> OptErr -> OptErr
onOpts f (opts, errs) = (f opts, errs)
onCppOpts :: (CppOpts -> CppOpts) -> OptErr -> OptErr
onCppOpts f (opts, errs) = (opts { optCppOpts = f (optCppOpts opts) }, errs)
onPrepOpts :: (PrepOpts -> PrepOpts) -> OptErr -> OptErr
onPrepOpts f (opts, errs) = (opts { optPrepOpts = f (optPrepOpts opts) }, errs)
onWarnOpts :: (WarnOpts -> WarnOpts) -> OptErr -> OptErr
onWarnOpts f (opts, errs) = (opts { optWarnOpts = f (optWarnOpts opts) }, errs)
onDebugOpts :: (DebugOpts -> DebugOpts) -> OptErr -> OptErr
onDebugOpts f (opts, errs)
= (opts { optDebugOpts = f (optDebugOpts opts) }, errs)
withArg :: ((a -> b) -> OptErr -> OptErr)
-> (String -> a -> b) -> String -> OptErr -> OptErr
withArg lift f arg = lift (f arg)
addErr :: String -> OptErr -> OptErr
addErr err (opts, errs) = (opts, errs ++ [err])
mkOptDescr :: ((opt -> opt) -> OptErr -> OptErr)
-> String -> [String] -> String -> String -> OptErrTable opt
-> OptDescr (OptErr -> OptErr)
mkOptDescr lift flags longFlags arg what tbl = Option flags longFlags
(ReqArg (parseOptErr lift what tbl) arg)
("set " ++ what ++ " `" ++ arg ++ "', where `" ++ arg ++ "' is one of\n"
++ renderOptErrTable tbl)
parseOptErr :: ((opt -> opt) -> OptErr -> OptErr)
-> String -> OptErrTable opt -> String -> OptErr -> OptErr
parseOptErr lift what table opt = case lookup3 opt table of
Just f -> lift f
Nothing -> addErr $ "unrecognized " ++ what ++ '`' : opt ++ "'\n"
where
lookup3 _ [] = Nothing
lookup3 k ((k', _, v2) : kvs)
| k == k' = Just v2
| otherwise = lookup3 k kvs
renderOptErrTable :: OptErrTable opt -> String
renderOptErrTable ds
= intercalate "\n" $ map (\(k, d, _) -> " " ++ rpad maxLen k ++ ": " ++ d) ds
where
maxLen = maximum $ map (\(k, _, _) -> length k) ds
rpad n x = x ++ replicate (n - length x) ' '
options :: [OptDescr (OptErr -> OptErr)]
options =
[ Option "h?" ["help"]
(NoArg (onOpts $ \ opts -> opts { optMode = ModeHelp }))
"display this help and exit"
, Option "V" ["version"]
(NoArg (onOpts $ \ opts -> opts { optMode = ModeVersion }))
"show the version number and exit"
, Option "" ["numeric-version"]
(NoArg (onOpts $ \ opts -> opts { optMode = ModeNumericVersion }))
"show the numeric version number and exit"
, mkOptDescr onOpts "v" ["verbosity"] "n" "verbosity level" verbDescriptions
, Option "q" ["no-verb"]
(NoArg (onOpts $ \ opts -> opts { optVerbosity = VerbQuiet } ))
"set verbosity level to quiet"
, Option "f" ["force"]
(NoArg (onOpts $ \ opts -> opts { optForce = True }))
"force compilation of target file"
, Option "P" ["lib-dir"]
(ReqArg (withArg onOpts $ \ arg opts -> opts { optLibraryPaths =
nub $ optLibraryPaths opts ++ splitSearchPath arg}) "dir[:dir]")
"search for libraries in dir[:dir]"
, Option "i" ["import-dir"]
(ReqArg (withArg onOpts $ \ arg opts -> opts { optImportPaths =
nub $ optImportPaths opts ++
map (normalise . addTrailingPathSeparator) (splitSearchPath arg)
}) "dir[:dir]")
"search for imports in dir[:dir]"
, Option [] ["htmldir"]
(ReqArg (withArg onOpts $ \ arg opts -> opts { optHtmlDir =
Just arg }) "dir")
"write HTML documentation into directory `dir'"
, Option "" ["no-subdir"]
(NoArg (onOpts $ \ opts -> opts { optUseSubdir = False }))
("disable writing to `" ++ currySubdir ++ "' subdirectory")
, Option "" ["no-intf"]
(NoArg (onOpts $ \ opts -> opts { optInterface = False }))
"do not create an interface file"
, Option "" ["no-warn"]
(NoArg (onWarnOpts $ \ opts -> opts { wnWarn = False }))
"do not print warnings"
, Option "" ["no-overlap-warn"]
(NoArg (onWarnOpts $ \ opts -> opts {wnWarnFlags =
addFlag WarnOverlapping (wnWarnFlags opts) }))
"do not print warnings for overlapping rules"
, targetOption Tokens "tokens"
"generate token stream"
, targetOption Comments "comments"
"generate comments stream"
, targetOption Parsed "parse-only"
"generate source representation"
, targetOption FlatCurry "flat"
"generate FlatCurry code"
, targetOption TypedFlatCurry "typed-flat"
"generate typed FlatCurry code"
, targetOption TypeAnnotatedFlatCurry "type-annotated-flat"
"generate type-annotated FlatCurry code"
, targetOption AbstractCurry "acy"
"generate typed AbstractCurry"
, targetOption UntypedAbstractCurry "uacy"
"generate untyped AbstractCurry"
, targetOption Html "html"
"generate html documentation"
, targetOption AST "ast"
"generate abstract syntax tree"
, targetOption ShortAST "short-ast"
"generate shortened abstract syntax tree for documentation"
, Option "F" []
(NoArg (onPrepOpts $ \ opts -> opts { ppPreprocess = True }))
"use custom preprocessor"
, Option "" ["pgmF"]
(ReqArg (withArg onPrepOpts $ \ arg opts -> opts { ppCmd = arg})
"cmd")
"execute preprocessor command <cmd>"
, Option "" ["optF"]
(ReqArg (withArg onPrepOpts $ \ arg opts ->
opts { ppOpts = ppOpts opts ++ [arg]}) "option")
"execute preprocessor with option <option>"
, Option "e" ["extended"]
(NoArg (onOpts $ \ opts -> opts { optExtensions =
nub $ kielExtensions ++ optExtensions opts }))
"enable extended Curry functionalities"
, mkOptDescr onOpts "c" ["case-mode"] "mode" "case mode" caseModeDescriptions
, mkOptDescr onOpts "X" [] "ext" "language extension" extDescriptions
, mkOptDescr onWarnOpts "W" [] "opt" "warning option" warnDescriptions
, mkOptDescr onDebugOpts "d" [] "opt" "debug option" debugDescriptions
, Option "" ["cpp"]
(NoArg (onCppOpts $ \ opts -> opts { cppRun = True }))
"run C preprocessor"
, Option "D" []
(ReqArg (withArg ($) parseCppDefinition) "s=v")
"define symbol `s` with value `v` for the C preprocessor"
]
parseCppDefinition :: String -> OptErr -> OptErr
parseCppDefinition arg optErr
| not (null s) && not (null v) && all isDigit v
= onCppOpts (addCppDefinition s v) optErr
| otherwise
= addErr (cppDefinitionErr arg) optErr
where (s, v) = fmap (drop 1) $ break ('=' ==) arg
addCppDefinition :: String -> String -> CppOpts -> CppOpts
addCppDefinition s v opts =
opts { cppDefinitions = Map.insert s (read v) (cppDefinitions opts) }
cppDefinitionErr :: String -> String
cppDefinitionErr = (++) "Invalid format for option '-D': "
targetOption :: TargetType -> String -> String -> OptDescr (OptErr -> OptErr)
targetOption ty flag desc
= Option "" [flag] (NoArg (onOpts $ \ opts -> opts { optTargetTypes =
nub $ ty : optTargetTypes opts })) desc
verbDescriptions :: OptErrTable Options
verbDescriptions = map toDescr verbosities
where
toDescr (flag, name, desc)
= (name, desc, \ opts -> opts { optVerbosity = flag })
extDescriptions :: OptErrTable Options
extDescriptions = map toDescr extensions
where
toDescr (flag, name, desc)
= (name, desc,
\opts -> let cppOpts = optCppOpts opts
in opts { optCppOpts =
cppOpts { cppRun = cppRun cppOpts || flag == CPP }
, optExtensions = addFlag flag (optExtensions opts)
})
caseModeDescriptions :: OptErrTable Options
caseModeDescriptions
= [ ( "free" , "use free case mode"
, \ opts -> opts { optCaseMode = CaseModeFree } )
, ( "haskell", "use haskell style case mode"
, \ opts -> opts { optCaseMode = CaseModeHaskell } )
, ( "prolog" , "use prolog style case mode"
, \ opts -> opts { optCaseMode = CaseModeProlog } )
, ( "goedel" , "use goedel case mode"
, \ opts -> opts { optCaseMode = CaseModeGoedel } )
]
warnDescriptions :: OptErrTable WarnOpts
warnDescriptions
= [ ( "all" , "turn on all warnings"
, \ opts -> opts { wnWarnFlags = [minBound .. maxBound] } )
, ("none" , "turn off all warnings"
, \ opts -> opts { wnWarnFlags = [] } )
, ("error", "treat warnings as errors"
, \ opts -> opts { wnWarnAsError = True } )
] ++ map turnOn warnFlags ++ map turnOff warnFlags
where
turnOn (flag, name, desc)
= (name, "warn for " ++ desc
, \ opts -> opts { wnWarnFlags = addFlag flag (wnWarnFlags opts)})
turnOff (flag, name, desc)
= ("no-" ++ name, "do not warn for " ++ desc
, \ opts -> opts { wnWarnFlags = removeFlag flag (wnWarnFlags opts)})
debugDescriptions :: OptErrTable DebugOpts
debugDescriptions =
[ ( "dump-all" , "dump everything"
, \ opts -> opts { dbDumpLevels = [minBound .. maxBound] })
, ( "dump-none" , "dump nothing"
, \ opts -> opts { dbDumpLevels = [] })
, ( "dump-env" , "additionally dump compiler environment"
, \ opts -> opts { dbDumpEnv = True })
, ( "dump-raw" , "dump as raw AST (instead of pretty printing)"
, \ opts -> opts { dbDumpRaw = True })
, ( "dump-all-bindings" , "when dumping bindings, dump all instead of just local ones"
, \ opts -> opts { dbDumpAllBindings = True })
, ( "dump-simple" , "print a simplified, more readable environment"
, \ opts -> opts { dbDumpSimple = True })
] ++ map toDescr dumpLevel
where
toDescr (flag, name, desc)
= (name , "dump code after " ++ desc
, \ opts -> opts { dbDumpLevels = addFlag flag (dbDumpLevels opts)})
addFlag :: Eq a => a -> [a] -> [a]
addFlag o opts = nub $ o : opts
removeFlag :: Eq a => a -> [a] -> [a]
removeFlag o opts = filter (/= o) opts
updateOpts :: Options -> [String] -> (Options, [String], [String])
updateOpts opts args = (opts', files, errs ++ errs2 ++ checkOpts opts files)
where
(opts', errs2) = foldl (flip ($)) (opts, []) optErrs
(optErrs, files, errs) = getOpt Permute options args
parseOpts :: [String] -> (Options, [String], [String])
parseOpts = updateOpts defaultOptions
checkOpts :: Options -> [String] -> [String]
checkOpts opts _
= [ "The option '--htmldir' is only valid for HTML generation mode"
| isJust (optHtmlDir opts) && Html `notElem` optTargetTypes opts ]
usage :: String -> String
usage prog = usageInfo header options
where header = "usage: " ++ prog ++ " [OPTION] ... MODULES ..."
getCompilerOpts :: IO (String, Options, [String], [String])
getCompilerOpts = do
args <- getArgs
prog <- getProgName
let (opts, files, errs) = parseOpts args
return (prog, opts, files, errs)