module GF.Command.Commands ( allCommands, lookCommand, exec, isOpt, options, flags, CommandInfo, CommandOutput ) where import PGF import PGF.CId import PGF.ShowLinearize import PGF.Macros import PGF.Data ---- import PGF.Morphology import PGF.Quiz import PGF.VisualizeTree import GF.Compile.Export import GF.Infra.Option (noOptions) import GF.Infra.UseIO import GF.Data.ErrM ---- import PGF.Expr (readTree) import GF.Command.Abstract import GF.Text.Lexing import GF.Text.Transliterations import GF.Data.Operations import Data.Maybe import qualified Data.Map as Map import System.Cmd import Debug.Trace type CommandOutput = ([Tree],String) ---- errors, etc data CommandInfo = CommandInfo { exec :: [Option] -> [Tree] -> IO CommandOutput, synopsis :: String, syntax :: String, explanation :: String, longname :: String, options :: [(String,String)], flags :: [(String,String)], examples :: [String] } emptyCommandInfo :: CommandInfo emptyCommandInfo = CommandInfo { exec = \_ ts -> return (ts,[]), ---- synopsis = "", syntax = "", explanation = "", longname = "", options = [], flags = [], examples = [] } lookCommand :: String -> Map.Map String CommandInfo -> Maybe CommandInfo lookCommand = Map.lookup commandHelpAll :: PGF -> [Option] -> String commandHelpAll pgf opts = unlines [commandHelp (isOpt "full" opts) (co,info) | (co,info) <- Map.assocs (allCommands pgf)] commandHelp :: Bool -> (String,CommandInfo) -> String commandHelp full (co,info) = unlines $ [ co ++ ", " ++ longname info, synopsis info] ++ if full then [ "", "syntax:" ++++ " " ++ syntax info, "", explanation info, "options:" ++++ unlines [" -" ++ o ++ "\t" ++ e | (o,e) <- options info], "flags:" ++++ unlines [" -" ++ o ++ "\t" ++ e | (o,e) <- flags info], "examples:" ++++ unlines [" " ++ s | s <- examples info] ] else [] -- this list must no more be kept sorted by the command name allCommands :: PGF -> Map.Map String CommandInfo allCommands pgf = Map.fromList [ ("cc", emptyCommandInfo { longname = "compute_concrete", syntax = "cc (-all | -table | -unqual)? TERM", synopsis = "computes concrete syntax term using a source grammar", explanation = unlines [ "Compute TERM by concrete syntax definitions. Uses the topmost", "module (the last one imported) to resolve constant names.", "N.B.1 You need the flag -retain when importing the grammar, if you want", "the definitions to be retained after compilation.", "N.B.2 The resulting term is not a tree in the sense of abstract syntax", "and hence not a valid input to a Tree-expecting command.", "This command must be a line of its own, and thus cannot be a part", "of a pipe." ], options = [ ("all","pick all strings (forms and variants) from records and tables"), ("table","show all strings labelled by parameters"), ("unqual","hide qualifying module names") ] }), ("dc", emptyCommandInfo { longname = "define_command", syntax = "dc IDENT COMMANDLINE", synopsis = "define a command macro", explanation = unlines [ "Defines IDENT as macro for COMMANDLINE, until IDENT gets redefined.", "A call of the command has the form %IDENT. The command may take an", "argument, which in COMMANDLINE is marked as ?0. Both strings and", "trees can be arguments. Currently at most one argument is possible.", "This command must be a line of its own, and thus cannot be a part", "of a pipe." ] }), ("dt", emptyCommandInfo { longname = "define_tree", syntax = "dt IDENT (TREE | STRING | \"<\" COMMANDLINE)", synopsis = "define a tree or string macro", explanation = unlines [ "Defines IDENT as macro for TREE or STRING, until IDENT gets redefined.", "The defining value can also come from a command, preceded by \"<\".", "If the command gives many values, the first one is selected.", "A use of the macro has the form %IDENT. Currently this use cannot be", "a subtree of another tree. This command must be a line of its own", "and thus cannot be a part of a pipe." ], examples = [ ("dt ex \"hello world\" -- define ex as string"), ("dt ex UseN man_N -- define ex as string"), ("dt ex < p -cat=NP \"the man in the car\" -- define ex as parse result"), ("l -lang=LangSwe %ex | ps -to_utf8 -- linearize the tree ex") ] }), ("e", emptyCommandInfo { longname = "empty", synopsis = "empty the environment" }), ("gr", emptyCommandInfo { longname = "generate_random", synopsis = "generate random trees in the current abstract syntax", syntax = "gr [-cat=CAT] [-number=INT]", examples = [ "gr -- one tree in the startcat of the current grammar", "gr -cat=NP -number=16 -- 16 trees in the category NP" ], explanation = unlines [ "Generates a list of random trees, by default one tree." ---- "If a tree argument is given, the command completes the Tree with values to", ---- "the metavariables in the tree." ], flags = [ ("cat","generation category"), ("lang","excludes functions that have no linearization in this language"), ("number","number of trees generated") ], exec = \opts _ -> do let pgfr = optRestricted opts ts <- generateRandom pgfr (optCat opts) return $ fromTrees $ take (optNum opts) ts }), ("gt", emptyCommandInfo { longname = "generate_trees", synopsis = "generates a list of trees, by default exhaustive", explanation = unlines [ "Generates all trees of a given category, with increasing depth.", "By default, the depth is 4, but this can be changed by a flag." ---- "If a Tree argument is given, the command completes the Tree with values", ---- "to the metavariables in the tree." ], flags = [ ("cat","the generation category"), ("depth","the maximum generation depth"), ("lang","excludes functions that have no linearization in this language"), ("number","the number of trees generated") ], exec = \opts _ -> do let pgfr = optRestricted opts let dp = return $ valIntOpts "depth" 4 opts let ts = generateAllDepth pgfr (optCat opts) dp return $ fromTrees $ take (optNumInf opts) ts }), ("h", emptyCommandInfo { longname = "help", syntax = "h (-full)? COMMAND?", synopsis = "get description of a command, or a the full list of commands", explanation = unlines [ "Displays information concerning the COMMAND.", "Without argument, shows the synopsis of all commands." ], options = [ ("full","give full information of the commands") ], exec = \opts ts -> return ([], case ts of [t] -> let co = showTree t in case lookCommand co (allCommands pgf) of ---- new map ??!! Just info -> commandHelp True (co,info) _ -> "command not found" _ -> commandHelpAll pgf opts) }), ("i", emptyCommandInfo { longname = "import", synopsis = "import a grammar from source code or compiled .pgf file", explanation = unlines [ "Reads a grammar from File and compiles it into a GF runtime grammar.", "If a grammar with the same concrete name is already in the state", "it is overwritten - but only if compilation succeeds.", "The grammar parser depends on the file name suffix:", " .gf normal GF source", " .gfo compiled GF source", " .pgf precompiled grammar in Portable Grammar Format" ], options = [ -- ["prob", "retain", "gfo", "src", "no-cpu", "cpu", "quiet", "verbose"] ("retain","retain operations (used for cc command)"), ("src", "force compilation from source"), ("v", "be verbose - show intermediate status information") ] }), ("l", emptyCommandInfo { longname = "linearize", synopsis = "convert an abstract syntax expression to string", explanation = unlines [ "Shows the linearization of a Tree by the grammars in scope.", "The -lang flag can be used to restrict this to fewer languages.", "A sequence of string operations (see command ps) can be given", "as options, and works then like a pipe to the ps command, except", "that it only affect the strings, not e.g. the table labels." ], examples = [ "l -langs=LangSwe,LangNor no_Utt -- linearize tree to LangSwe and LangNor", "gr -lang=LangHin -cat=Cl | l -table -to_devanagari -to_utf8 -- hindi table" ], exec = \opts -> return . fromStrings . map (optLin opts), options = [ ("all","show all forms and variants"), ("record","show source-code-like record"), ("table","show all forms labelled by parameters"), ("term", "show PGF term"), ("treebank","show the tree and tag linearizations with language names") ] ++ stringOpOptions, flags = [ ("lang","the languages of linearization (comma-separated, no spaces)") ] }), ("ma", emptyCommandInfo { longname = "morpho_analyse", synopsis = "print the morphological analyses of all words in the string", explanation = unlines [ "Prints all the analyses of space-separated words in the input string,", "using the morphological analyser of the actual grammar (see command pf)" ], exec = \opts -> return . fromString . unlines . map prMorphoAnalysis . concatMap (morphos opts) . concatMap words . toStrings }), ("mq", emptyCommandInfo { longname = "morpho_quiz", synopsis = "start a morphology quiz", exec = \opts _ -> do let lang = optLang opts let cat = optCat opts morphologyQuiz pgf lang cat return void, flags = [ ("lang","language of the quiz"), ("cat","category of the quiz"), ("number","maximum number of questions") ] }), ("p", emptyCommandInfo { longname = "parse", synopsis = "parse a string to abstract syntax expression", explanation = unlines [ "Shows all trees returned by parsing a string in the grammars in scope.", "The -lang flag can be used to restrict this to fewer languages.", "The default start category can be overridden by the -cat flag.", "See also the ps command for lexing and character encoding." ], exec = \opts -> return . fromTrees . concatMap (par opts) . toStrings, flags = [ ("cat","target category of parsing"), ("lang","the languages of parsing (comma-separated, no spaces)") ] }), ("pg", emptyCommandInfo { ----- longname = "print_grammar", synopsis = "print the actual grammar with the given printer", explanation = unlines [ "Prints the actual grammar, with all involved languages.", "In some printers, this can be restricted to a subset of languages", "with the -lang=X,Y flag (comma-separated, no spaces).", "The -printer=P flag sets the format in which the grammar is printed.", "N.B.1 Since grammars are compiled when imported, this command", "generally shows a grammar that looks rather different from the source.", "N.B.2 This command is slightly obsolete: to produce different formats", "the batch compiler gfc is recommended, and has many more options." ], exec = \opts _ -> return $ fromString $ prGrammar opts, flags = [ --"cat", ("lang", "select languages for the some options (default all languages)"), ("printer","select the printing format (see gfc --help)") ], options = [ ("cats", "show just the names of abstract syntax categories"), ("fullform", "print the fullform lexicon"), ("missing","show just the names of functions that have no linearization") ] }), ("ph", emptyCommandInfo { longname = "print_history", synopsis = "print command history", explanation = unlines [ "Prints the commands issued during the GF session.", "The result is readable by the eh command.", "The result can be used as a script when starting GF." ], examples = [ "ph | wf -file=foo.gfs -- save the history into a file" ] }), ("ps", emptyCommandInfo { longname = "put_string", syntax = "ps OPT? STRING", synopsis = "return a string, possibly processed with a function", explanation = unlines [ "Returns a string obtained from its argument string by applying", "string processing functions in the order given in the command line", "option list. Thus 'ps -f -g s' returns g (f s). Typical string processors", "are lexers and unlexers, but also character encoding conversions are possible.", "The unlexers preserve the division of their input to lines.", "To see transliteration tables, use command ut." ], examples = [ "l (EAdd 3 4) | ps -code -- linearize code-like output", "ps -lexer=code | p -cat=Exp -- parse code-like input", "gr -cat=QCl | l | ps -bind -to_utf8 -- linearization output from LangFin", "ps -from_utf8 \"jag ?r h?r\" | p -- parser in LangSwe in UTF8 terminal", "ps -to_devanagari -to_utf8 \"A-p\" -- show Devanagari in UTF8 terminal" ], exec = \opts -> return . fromString . stringOps (map prOpt opts) . toString, options = stringOpOptions }), ("q", emptyCommandInfo { longname = "quit", synopsis = "exit GF interpreter" }), ("rf", emptyCommandInfo { longname = "read_file", synopsis = "read string or tree input from a file", explanation = unlines [ "Reads input from file. The filename must be in double quotes.", "The input is interpreted as a string by default, and can hence be", "piped e.g. to the parse command. The option -tree interprets the", "input as a tree, which can be given e.g. to the linearize command.", "The option -lines will result in a list of strings or trees, one by line." ], options = [ ("lines","return the list of lines, instead of the singleton of all contents"), ("tree","convert strings into trees") ], exec = \opts arg -> do let file = valIdOpts "file" "_gftmp" opts s <- readFile file return $ case opts of _ | isOpt "lines" opts && isOpt "tree" opts -> fromTrees [t | l <- lines s, Just t <- [readTree l]] _ | isOpt "tree" opts -> fromTrees [t | Just t <- [readTree s]] _ | isOpt "lines" opts -> fromStrings $ lines s _ -> fromString s, flags = [("file","the input file name")] }), ("tq", emptyCommandInfo { longname = "translation_quiz", synopsis = "start a translation quiz", exec = \opts _ -> do let from = valIdOpts "from" (optLang opts) opts let to = valIdOpts "to" (optLang opts) opts let cat = optCat opts translationQuiz pgf from to cat return void, flags = [ ("from","translate from this language"), ("to","translate to this language"), ("cat","translate in this category"), ("number","the maximum number of questions") ] }), ("sp", emptyCommandInfo { longname = "system_pipe", synopsis = "send argument to a system command", syntax = "sp -command=\"SYSTEMCOMMAND\" STRING", exec = \opts arg -> do let tmpi = "_tmpi" --- let tmpo = "_tmpo" writeFile tmpi $ toString arg let syst = optComm opts ++ " " ++ tmpi system $ syst ++ " <" ++ tmpi ++ " >" ++ tmpo s <- readFile tmpo return $ fromString s, flags = [ ("command","the system command applied to the argument") ], examples = [ "ps -command=\"wc\" \"foo\"", "gt | l | sp -command=\"grep \\\"who\\\"\" | sp -command=\"wc\"" ] }), ("ut", emptyCommandInfo { longname = "unicode_table", synopsis = "show a transliteration table for a unicode character set", exec = \opts arg -> do let t = concatMap prOpt (take 1 opts) let out = maybe "no such transliteration" characterTable $ transliteration t return $ fromString out, options = [ ("devanagari","Devanagari"), ("thai", "Thai") ] }), ("vt", emptyCommandInfo { longname = "visualize_tree", synopsis = "show a set of trees graphically", explanation = unlines [ "Prints a set of trees in the .dot format (the graphviz format).", "The graph can be saved in a file by the wf command as usual.", "If the -view flag is defined, the graph is saved in a temporary file", "which is processed by graphviz and displayed by the program indicated", "by the flag. The target format is postscript, unless overridden by the", "flag -format." ], exec = \opts ts -> do let funs = not (isOpt "nofun" opts) let cats = not (isOpt "nocat" opts) let grph = visualizeTrees pgf (funs,cats) ts -- True=digraph if isFlag "view" opts || isFlag "format" opts then do let file s = "_grph." ++ s let view = optViewGraph opts ++ " " let format = optViewFormat opts writeFile (file "dot") grph system $ "dot -T" ++ format ++ " " ++ file "dot" ++ " > " ++ file format ++ " ; " ++ view ++ file format return void else return $ fromString grph, examples = [ "p \"hello\" | vt -- parse a string and show trees as graph script", "p \"hello\" | vt -view=\"open\" -- parse a string and display trees on a Mac" ], options = [ ("nofun","don't show functions but only categories"), ("nocat","don't show categories but only functions") ], flags = [ ("format","format of the visualization file (default \"ps\")"), ("view","program to open the resulting file (default \"gv\")") ] }), ("wf", emptyCommandInfo { longname = "write_file", synopsis = "send string or tree to a file", exec = \opts arg -> do let file = valIdOpts "file" "_gftmp" opts if isOpt "append" opts then appendFile file (toString arg) else writeFile file (toString arg) return void, options = [ ("append","append to file, instead of overwriting it") ], flags = [("file","the output filename")] }) ] where lin opts t = unlines [linearize pgf lang t | lang <- optLangs opts] par opts s = concat [parse pgf lang (optCat opts) s | lang <- optLangs opts] void = ([],[]) optLin opts t = case opts of _ | isOpt "treebank" opts -> treebank opts t _ -> unlines [linear opts lang t | lang <- optLangs opts] linear opts lang = let unl = unlex opts lang in case opts of _ | isOpt "all" opts -> allLinearize unl pgf (mkCId lang) _ | isOpt "table" opts -> tableLinearize unl pgf (mkCId lang) _ | isOpt "term" opts -> termLinearize pgf (mkCId lang) _ | isOpt "record" opts -> recordLinearize pgf (mkCId lang) _ -> unl . linearize pgf lang treebank opts t = unlines $ (abstractName pgf ++ ": " ++ showTree t) : [lang ++ ": " ++ linear opts lang t | lang <- optLangs opts] -- logic of coding in unlexing: -- - If lang has no coding flag, or -to_utf8 is not in opts, just opts are used. -- - If lang has flag coding=utf8, -to_utf8 is ignored. -- - If lang has coding=other, and -to_utf8 is in opts, from_other is applied first. unlex opts lang = {- trace (unwords optsC) $ -} stringOps optsC where optsC = case lookFlag pgf lang "coding" of Just "utf8" -> filter (/="to_utf8") $ map prOpt opts Just other | isOpt "to_utf8" opts -> let cod = ("from_" ++ other) in cod : filter (/=cod) (map prOpt opts) _ -> map prOpt opts optRestricted opts = restrictPGF (hasLin pgf (mkCId (optLang opts))) pgf optLangs opts = case valIdOpts "lang" "" opts of "" -> languages pgf lang -> chunks ',' lang optLang opts = head $ optLangs opts ++ ["#NOLANG"] optCat opts = valIdOpts "cat" (lookStartCat pgf) opts optComm opts = valStrOpts "command" "" opts optViewFormat opts = valStrOpts "format" "ps" opts optViewGraph opts = valStrOpts "view" "gv" opts optNum opts = valIntOpts "number" 1 opts optNumInf opts = valIntOpts "number" 1000000000 opts ---- 10^9 fromTrees ts = (ts,unlines (map showTree ts)) fromStrings ss = (map (Lit . LStr) ss, unlines ss) fromString s = ([Lit (LStr s)], s) toStrings ts = [s | Lit (LStr s) <- ts] toString ts = unwords [s | Lit (LStr s) <- ts] prGrammar opts = case opts of _ | isOpt "cats" opts -> unwords $ categories pgf _ | isOpt "fullform" opts -> concatMap (prFullFormLexicon . buildMorpho pgf . mkCId) $ optLangs opts _ | isOpt "missing" opts -> unlines $ [unwords (la:":": map prCId cs) | la <- optLangs opts, let cs = missingLins pgf (mkCId la)] _ -> case valIdOpts "printer" "pgf" opts of v -> concatMap snd $ exportPGF noOptions (read v) pgf morphos opts s = [lookupMorpho (buildMorpho pgf (mkCId la)) s | la <- optLangs opts] -- ps -f -g s returns g (f s) stringOps opts s = foldr app s (reverse opts) where app f = maybe id id (stringOp f) stringOpOptions = [ ("bind","bind tokens separated by Prelude.BIND, i.e. &+"), ("chars","lexer that makes every non-space character a token"), ("from_devanagari","from unicode to GF Devanagari transliteration"), ("from_thai","from unicode to GF Thai transliteration"), ("from_utf8","decode from utf8"), ("lextext","text-like lexer"), ("lexcode","code-like lexer"), ("lexmixed","mixture of text and code (code between $...$)"), ("to_devanagari","from GF Devanagari transliteration to unicode"), ("to_html","wrap in a html file with linebreaks"), ("to_thai","from GF Thai transliteration to unicode"), ("to_utf8","encode to utf8"), ("unlextext","text-like unlexer"), ("unlexcode","code-like unlexer"), ("unlexmixed","mixture of text and code (code between $...$)"), ("unchars","unlexer that puts no spaces between tokens"), ("unwords","unlexer that puts a single space between tokens (default)"), ("words","lexer that assumes tokens separated by spaces (default)") ] translationQuiz :: PGF -> Language -> Language -> Category -> IO () translationQuiz pgf ig og cat = do tts <- translationList pgf ig og cat infinity mkQuiz "Welcome to GF Translation Quiz." tts morphologyQuiz :: PGF -> Language -> Category -> IO () morphologyQuiz pgf ig cat = do tts <- morphologyList pgf ig cat infinity mkQuiz "Welcome to GF Morphology Quiz." tts -- | the maximal number of precompiled quiz problems infinity :: Int infinity = 256 lookFlag :: PGF -> String -> String -> Maybe String lookFlag pgf lang flag = lookConcrFlag pgf (mkCId lang) (mkCId flag)