----------------------------------------------------------------------
-- |
-- Module      : Option
-- Maintainer  : AR
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/11/14 16:03:41 $ 
-- > CVS $Author: aarne $
-- > CVS $Revision: 1.34 $
--
-- Options and flags used in GF shell commands and files.
--
-- The types 'Option' and 'Options' should be kept abstract, but:
--
--   - The constructor 'Opt' is used in "ShellCommands" and "GrammarToSource"
--
--   - The constructor 'Opts' us udes in "API", "Shell" and "ShellCommands"
-----------------------------------------------------------------------------

module GF.Infra.Option where

import Data.List (partition)
import Data.Char (isDigit)

-- * all kinds of options, to be kept abstract

newtype Option = Opt (String,[String]) deriving (Eq,Show,Read)
newtype Options = Opts [Option] deriving (Eq,Show,Read)

noOptions :: Options
noOptions = Opts []

-- | simple option -o
iOpt :: String -> Option
iOpt o = Opt (o,[])   

-- | option with argument -o=a
aOpt :: String -> String -> Option
aOpt o a = Opt (o,[a])  

iOpts :: [Option] -> Options
iOpts = Opts

-- | value of option argument
oArg :: String -> String
oArg s = s 

oElem :: Option -> Options -> Bool
oElem o (Opts os) = elem o os

eqOpt :: String -> Option -> Bool
eqOpt s (Opt (o, [])) = s == o
eqOpt s _ = False

type OptFun = String -> Option
type OptFunId = String

getOptVal :: Options -> OptFun -> Maybe String
getOptVal (Opts os) fopt = 
  case [a | opt@(Opt (o,[a])) <- os, opt == fopt a] of
    a:_ -> Just a
    _ -> Nothing

isSetFlag :: Options -> OptFun -> Bool
isSetFlag (Opts os) fopt = 
  case [a | opt@(Opt (o,[a])) <- os, opt == fopt a] of
    a:_ -> True
    _ -> False

getOptInt :: Options -> OptFun -> Maybe Int
getOptInt opts f = do
  s <- getOptVal opts f
  if (not (null s) && all isDigit s) then return (read s) else Nothing 

optIntOrAll :: Options -> OptFun -> [a] -> [a]
optIntOrAll opts f = case getOptInt opts f of
  Just i -> take i
  _ -> id

optIntOrN :: Options -> OptFun -> Int -> Int
optIntOrN opts f n = case getOptInt opts f of
  Just i -> i
  _ -> n

optIntOrOne :: Options -> OptFun -> Int
optIntOrOne opts f = optIntOrN opts f 1

changeOptVal :: Options -> OptFun -> String -> Options
changeOptVal os f x = 
  addOption (f x) $ maybe os (\y -> removeOption (f y) os) $ getOptVal os f

addOption :: Option -> Options -> Options
addOption o (Opts os) = iOpts (o:os)

addOptions :: Options -> Options -> Options
addOptions (Opts os) os0 = foldr addOption os0 os

concatOptions :: [Options] -> Options
concatOptions = foldr addOptions noOptions

removeOption :: Option -> Options -> Options
removeOption o (Opts os) = iOpts (filter (/=o) os)

removeOptions :: Options -> Options -> Options
removeOptions (Opts os) os0 = foldr removeOption os0 os

options :: [Option] -> Options
options = foldr addOption noOptions

unionOptions :: Options -> Options -> Options
unionOptions (Opts os) (Opts os') = Opts (os ++ os')

-- * parsing options, with prefix pre (e.g. \"-\")

getOptions :: String -> [String] -> (Options, [String])
getOptions pre inp = let
  (os,rest) = span (isOption pre) inp -- options before args
  in
  (Opts (map (pOption pre) os), rest)

pOption :: String -> String -> Option
pOption pre s = case span (/= '=') (drop (length pre) s) of
  (f,_:a) -> aOpt f a
  (o,[])  -> iOpt o

isOption :: String -> String -> Bool
isOption pre = (==pre) . take (length pre)

-- * printing options, without prefix

prOpt :: Option -> String
prOpt (Opt (s,[])) = s
prOpt (Opt (s,xs)) = s ++ "=" ++ concat xs

prOpts :: Options -> String
prOpts (Opts os) = unwords $ map prOpt os

-- * a suggestion for option names

-- ** parsing

strictParse, forgiveParse, ignoreParse, literalParse, rawParse, firstParse :: Option
-- | parse as term instead of string
dontParse :: Option

strictParse  = iOpt "strict"
forgiveParse = iOpt "n"
ignoreParse  = iOpt "ign"
literalParse = iOpt "lit"
rawParse     = iOpt "raw"
firstParse   = iOpt "1"
dontParse    = iOpt "read" 

newParser, newerParser, newCParser, newMParser :: Option
newParser   = iOpt "new"
newerParser = iOpt "newer"
newCParser  = iOpt "cfg"
newMParser  = iOpt "mcfg"

{-
useParserMCFG, useParserMCFGviaCFG, useParserCFG, useParserCF :: Option

useParserMCFG = iOpt "mcfg"
useParserMCFGviaCFG = iOpt "mcfg-via-cfg"
useParserCFG = iOpt "cfg"
useParserCF = iOpt "cf"
-}

-- ** grammar formats

showAbstr, showXML, showOld, showLatex, showFullForm,
  showEBNF, showCF, showWords, showOpts,
  isCompiled, isHaskell, noCompOpers, retainOpers,
  noCF, checkCirc, noCheckCirc, lexerByNeed, useUTF8id :: Option
defaultGrOpts :: [Option]

showAbstr   = iOpt "abs"
showXML     = iOpt "xml"
showOld     = iOpt "old"
showLatex   = iOpt "latex"
showFullForm = iOpt "fullform" 
showEBNF    = iOpt "ebnf"
showCF      = iOpt "cf"
showWords   = iOpt "ws"
showOpts    = iOpt "opts"
-- showOptim   = iOpt "opt"
isCompiled  = iOpt "gfc"
isHaskell   = iOpt "gfhs"
noCompOpers = iOpt "nocomp"
retainOpers = iOpt "retain"
defaultGrOpts = []
noCF        = iOpt "nocf"
checkCirc   = iOpt "nocirc"
noCheckCirc = iOpt "nocheckcirc"
lexerByNeed = iOpt "cflexer"
useUTF8id   = iOpt "utf8id"
elimSubs    = iOpt "subs"

-- ** linearization

allLin, firstLin, distinctLin, dontLin,
  showRecord, showStruct, xmlLin, latexLin,
  tableLin, useUTF8, showLang, withMetas :: Option
defaultLinOpts :: [Option]

allLin      = iOpt "all"
firstLin    = iOpt "one"
distinctLin = iOpt "nub"
dontLin     = iOpt "show"
showRecord  = iOpt "record"
showStruct  = iOpt "structured"
xmlLin      = showXML
latexLin    = showLatex
tableLin    = iOpt "table"
defaultLinOpts = [firstLin]
useUTF8     = iOpt "utf8"
showLang    = iOpt "lang"
showDefs    = iOpt "defs"
withMetas   = iOpt "metas"

-- ** other

beVerbose, showInfo, beSilent, emitCode, getHelp,
  doMake, doBatch, notEmitCode, makeMulti, beShort,
  wholeGrammar, makeFudget, byLines, byWords, analMorpho,
  doTrace, noCPU, doCompute, optimizeCanon, optimizeValues,
  stripQualif, nostripQualif, showAll, fromSource :: Option

beVerbose    = iOpt "v"
invertGrep   = iOpt "v" --- same letter in unix
showInfo     = iOpt "i"
beSilent     = iOpt "s"
emitCode     = iOpt "o"
getHelp      = iOpt "help"
doMake       = iOpt "make"
doBatch      = iOpt "batch"
notEmitCode  = iOpt "noemit"
makeMulti    = iOpt "multi"
beShort      = iOpt "short"
wholeGrammar = iOpt "w"
makeFudget   = iOpt "f"
byLines      = iOpt "lines"
byWords      = iOpt "words"
analMorpho   = iOpt "morpho"
doTrace      = iOpt "tr"
noCPU        = iOpt "nocpu"
doCompute    = iOpt "c"
optimizeCanon = iOpt "opt"
optimizeValues = iOpt "val"
stripQualif   = iOpt "strip"
nostripQualif = iOpt "nostrip"
showAll      = iOpt "all"
showFields   = iOpt "fields"
showMulti    = iOpt "multi"
fromSource   = iOpt "src"
makeConcrete = iOpt "examples"
fromExamples = iOpt "ex"
openEditor = iOpt "edit"

-- ** mainly for stand-alone

useUnicode, optCompute, optCheck, optParaphrase, forJava :: Option

useUnicode    = iOpt "unicode"
optCompute    = iOpt "compute"
optCheck      = iOpt "typecheck"
optParaphrase = iOpt "paraphrase"
forJava       = iOpt "java"

-- ** for edit session

allLangs, absView :: Option

allLangs  = iOpt "All"
absView   = iOpt "Abs"

-- ** options that take arguments

useTokenizer, useUntokenizer, useParser, withFun, 
  useLanguage, useResource, speechLanguage, useFont,
  grammarFormat, grammarPrinter, filterString, termCommand,
  transferFun, forForms, menuDisplay, sizeDisplay, typeDisplay,
  noDepTypes, extractGr, pathList, uniCoding :: String -> Option
-- | used on command line
firstCat :: String -> Option
-- | used in grammar, to avoid clash w res word
gStartCat :: String -> Option

useTokenizer   = aOpt "lexer"
useUntokenizer = aOpt "unlexer"
useParser      = aOpt "parser"
-- useStrategy    = aOpt "strategy" -- parsing strategy
withFun        = aOpt "fun"
firstCat       = aOpt "cat"      
gStartCat      = aOpt "startcat" 
useLanguage    = aOpt "lang"
useResource    = aOpt "res"
speechLanguage = aOpt "language"
useFont        = aOpt "font"
grammarFormat  = aOpt "format"
grammarPrinter = aOpt "printer"
filterString   = aOpt "filter"
termCommand    = aOpt "transform"
transferFun    = aOpt "transfer"
forForms       = aOpt "forms"
menuDisplay    = aOpt "menu"
sizeDisplay    = aOpt "size"
typeDisplay    = aOpt "types"
noDepTypes     = aOpt "nodeptypes"
extractGr      = aOpt "extract"
pathList       = aOpt "path"
uniCoding      = aOpt "coding"
probFile       = aOpt "probs"
noparseFile    = aOpt "noparse"

-- peb 16/3-05:
gfcConversion :: String -> Option
gfcConversion  = aOpt "conversion"

useName, useAbsName, useCncName, useResName, 
  useFile, useOptimizer :: String -> Option

useName        = aOpt "name"
useAbsName     = aOpt "abs"
useCncName     = aOpt "cnc"
useResName     = aOpt "res"
useFile        = aOpt "file"
useOptimizer   = aOpt "optimize"

markLin :: String -> Option
markOptXML, markOptJava, markOptStruct, markOptFocus :: String

markLin        = aOpt "mark"
markOptXML     = oArg "xml"
markOptJava    = oArg "java"
markOptStruct  = oArg "struct"
markOptFocus   = oArg "focus"


-- ** refinement order

nextRefine :: String -> Option
firstRefine, lastRefine :: String

nextRefine  = aOpt "nextrefine"
firstRefine = oArg "first"
lastRefine  = oArg "last"

-- ** Boolean flags

flagYes, flagNo :: String

flagYes = oArg "yes"
flagNo  = oArg "no"

-- ** integer flags

flagDepth, flagAlts, flagLength, flagNumber, flagRawtrees :: String -> Option

flagDepth  = aOpt "depth"
flagAlts   = aOpt "alts"
flagLength = aOpt "length"
flagNumber = aOpt "number"
flagRawtrees = aOpt "rawtrees"

caseYesNo :: Options -> OptFun -> Maybe Bool
caseYesNo opts f = do
  v <- getOptVal opts f
  if v == flagYes   then return True
     else if v == flagNo then return False
     else Nothing
