summaryrefslogtreecommitdiff
path: root/src/GF/Shell.hs
blob: cdacb798963fcbbc3d6efb1ad8314acd89cd2efd (plain)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
----------------------------------------------------------------------
-- |
-- Module      : Shell
-- Maintainer  : AR
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/11/07 20:15:05 $ 
-- > CVS $Author: bringert $
-- > CVS $Revision: 1.50 $
--
-- GF shell command interpreter.
-----------------------------------------------------------------------------

module GF.Shell where

--- abstract away from these?
import GF.Data.Str
import qualified GF.Grammar.Grammar as G
import qualified GF.Infra.Ident as I
import qualified GF.Grammar.Compute as Co
import qualified GF.Compile.CheckGrammar as Ch
import qualified GF.Grammar.Lookup as L
import qualified GF.Canon.GFC as GFC
import qualified GF.Canon.Look as Look
import qualified GF.Canon.CMacros as CMacros
import qualified GF.Compile.GrammarToCanon as GrammarToCanon
import GF.Grammar.Values
import GF.UseGrammar.GetTree

import GF.Shell.ShellCommands

import GF.Visualization.VisualizeGrammar (visualizeCanonGrammar, visualizeSourceGrammar)
import GF.Visualization.VisualizeTree (visualizeTrees)
import GF.API
import GF.API.IOGrammar
import GF.Compile.Compile
---- import GFTex
import GF.Shell.TeachYourself -- also a subshell

import GF.UseGrammar.Randomized ---
import GF.UseGrammar.Editing (goFirstMeta) ---

import GF.Probabilistic.Probabilistic

import GF.Compile.ShellState
import GF.Infra.Option
import GF.UseGrammar.Information
import GF.Shell.HelpFile
import GF.Compile.PrOld
import GF.Grammar.PrGrammar

import Control.Monad (foldM,liftM)
import System (system)
import System.IO (hPutStrLn, stderr)
import System.Random (newStdGen) ----
import Data.List (nub,isPrefixOf)
import GF.Data.Zipper ----

import GF.Data.Operations
import GF.Infra.UseIO
import GF.Text.UTF8 (encodeUTF8)
import Data.Char (isDigit)
import Data.Maybe (fromMaybe)

import GF.System.Signal (runInterruptibly)

---- import qualified GrammarToGramlet as Gr
---- import qualified GrammarToCanonXML2 as Canon

-- AR 18/4/2000 - 7/11/2001

-- data Command moved to ShellCommands. AR 27/5/2004

type CommandLine = (CommandOpt, CommandArg,  [CommandOpt])

-- | term as returned by the command parser
type SrcTerm = G.Term 

-- | history & CPU
type HState  = (ShellState,([String],Integer,ShMacros,ShTerms)) 

type ShMacros = [(String,[String])] -- dc %c = ... #1 ... #2 ...
type ShTerms  = [(String,Tree)]     -- dt $e = f ...

type ShellIO = (HState, CommandArg) -> IO (HState, CommandArg)

initHState :: ShellState -> HState
initHState st = (st,([],0,[],[]))

cpuHState :: HState -> Integer
cpuHState (_,(_,i,_,_)) = i

optsHState :: HState -> Options
optsHState (st,_) = globalOptions st

putHStateCPU :: Integer -> HState -> HState
putHStateCPU cpu (st,(h,_,c,t)) = (st,(h,cpu,c,t))

updateHistory :: String -> HState -> HState
updateHistory s (st,(h,cpu,c,t)) = (st,(s:h,cpu,c,t))

addShMacro :: (String,[String]) -> HState -> HState
addShMacro m (st,(h,cpu,c,t)) = (st,(h,cpu,m:c,t))

addShTerm :: (String,Tree) -> HState -> HState
addShTerm m (st,(h,cpu,c,t)) = (st,(h,cpu,c,m:t))

resolveShMacro :: HState -> String -> [String] -> [String]
resolveShMacro st@(_,(_,_,cs,_)) c args = case lookup c cs of
  Just def -> map subst def
  _ -> [] ----
 where
   subst s = case s of
     "#1" -> unwords args
     _ -> s
 --- so far only one arg allowed - how to determine arg boundaries?
{-
   subst s = case s of
     '#':d@(_:_) | all isDigit d -> 
        let i = read d in if i > lg then s else args !! (i-1) -- #1 is first
     _ -> s
   lg = length args
-}

lookupShTerm :: HState -> String -> Maybe Tree
lookupShTerm st@(_,(_,_,_,ts)) c = lookup c ts

txtHelpMacros :: HState -> String
txtHelpMacros (_,(_,_,cs,ts)) = unlines $
  ["Defined commands:",""] ++
  [c +++ "=" +++ unwords def | (c,def) <- cs] ++
  ["","Defined terms:",""] ++
  [c +++ "=" +++ prt_ def | (c,def) <- ts]

-- | empty command if index over
earlierCommandH :: HState -> Int -> String
earlierCommandH (_,(h,_,_,_)) = ((h ++ repeat "") !!) 

execLinesH :: String -> [CommandLine] -> HState -> IO HState
execLinesH s cs hst@(st, (h,_,_,_)) = do
  (_,st') <- execLinesI True cs hst
  cpu     <- prOptCPU (optsHState st') (cpuHState hst)
  return $ putHStateCPU cpu $ updateHistory s st'

-- | Like 'execLines', but can be interrupted by SIGINT.
execLinesI :: Bool -> [CommandLine] -> HState -> IO ([String],HState)
execLinesI put cs st = 
    do
    x <- runInterruptibly (execLines put cs st)
    case x of
           Left ex -> do hPutStrLn stderr ""
                         hPutStrLn stderr $ show ex
                         return ([],st)
           Right y -> return y

ifImpure :: [CommandLine] -> Maybe (ImpureCommand,Options)
ifImpure cls = foldr (const . Just) Nothing [(c,os) | ((CImpure c,os),_,_) <- cls]

-- | the main function: execution of commands. 'put :: Bool' forces immediate output
--
-- command line with consecutive (;) commands: no value transmitted
execLines :: Bool -> [CommandLine] -> HState -> IO ([String],HState)
execLines put cs st = foldM (flip (execLine put)) ([],st) cs

-- | command line with piped (|) commands: no value returned
execLine :: Bool -> CommandLine -> ([String],HState) -> IO ([String],HState)
execLine put (c@(co, os), arg, cs) (outps,st) = do
  (st',val) <- execC c (st, arg)
  let tr = oElem doTrace os || null cs    -- option -tr leaves trace in pipe
      utf = if (oElem useUTF8 os) then encodeUTF8 else id 
      outp = if tr then [utf (prCommandArg val)] else []
  if put then mapM_ putStrLnFlush outp else return ()
  execs cs val (if put then [] else outps ++ outp, st')
 where
    execs []     arg st = return st
    execs (c:cs) arg st = execLine put (c, arg, cs) st

-- | individual commands possibly piped: value returned; this is not a state monad
execC :: CommandOpt -> ShellIO
execC co@(comm, opts0) sa@(sh@(st,(h,_,_,_)),a) = checkOptions st co >> case comm of

  CImport file | oElem fromExamples opts -> do
    es <- liftM nub $ getGFEFiles opts file
    system $ "gf -examples" +++ unlines es
    execC (comm, removeOption fromExamples opts) sa
  CImport file -> useIOE sa $ do
    st1 <- shellStateFromFiles opts st file
    ioeIO $ changeState (const st1) sa --- \ ((_,h),a) -> ((st,h), a))

  CEmptyState    -> changeState reinitShellState sa
  CChangeMain ma -> changeStateErr (changeMain ma) sa
  CStripState    -> changeState purgeShellState sa

{-
  CRemoveLanguage lan -> changeState (removeLanguage lan) sa
  CTransformGrammar file -> do
    s <- transformGrammarFile opts file
    returnArg (AString s) sa
  CConvertLatex file -> do
    s <- readFileIf file
    returnArg (AString (convertGFTex s)) sa 
-}
  CPrintHistory -> (returnArg $ AString $ unlines $ reverse h) sa
  -- good to have here for piping; eh and ec must be done on outer level

  CDefineCommand c args -> return (addShMacro (c,args) sh, AUnit)
  CDefineTerm c -> do 
    let 
      a' = case a of
        ASTrm _ -> s2t a
        AString _ -> s2t a
        _ -> a
    case a' of
      ATrms [trm] -> return (addShTerm (c,trm) sh, AUnit) 
      _ ->  returnArg (AError "illegal term definition") sa

  CLinearize [] 
    | oElem showMulti opts -> 
       changeArg (opTS2CommandArg (unlines. linearizeToAll
          (allStateGrammars st)) . s2t) sa 
    | otherwise -> changeArg (opTS2CommandArg (optLinearizeTreeVal opts gro) . s2t) sa
----  CLinearize m -> changeArg (opTS2CommandArg (optLinearizeArgForm opts gro m)) sa

  CParse 
----    | oElem showMulti opts -> do
    | oElem byLines opts -> do
        let ss = (if oElem showAll opts then id else filter (not . null)) $ 
                     lines $ prCommandArg a
        mts <- mapM parse ss
        let a' = ATrms [t | (_,ATrms ts) <- mts, t <- ts]
        changeArg (const a') sa
    | otherwise -> parse $ prCommandArg a
   where 
      parse x = do
       warnDiscont opts 
       let p = optParseArgErrMsg opts gro x
       case p of
         Ok (ts,msg) 
           | oElem (iOpt "prob") opts -> do
                let probs = stateProbs gro 
                let tps = rankByScore [(t,computeProbTree probs t) | t <- ts]
                putStrLnFlush msg
                mapM_ putStrLnFlush [show p | (t,p) <- tps]
                changeArg (const $ ATrms (map fst tps)) sa
           | otherwise -> putStrLnFlush msg >> changeArg (const $ ATrms ts) sa
         Bad msg -> changeArg (const $ AError (msg +++ "input" +++ x)) sa

  CTranslate il ol -> do
    let a' = opST2CommandArg (optParseArgErr opts (sgr il)) a
    returnArg (opTS2CommandArg (optLinearizeTreeVal opts (sgr ol)) a') sa

  CGenerateRandom | oElem showCF opts || oElem (iOpt "prob") opts -> do
    let probs = stateProbs gro 
    let cat = firstAbsCat opts gro
    let n = optIntOrN opts flagNumber 1
    gen    <- newStdGen
    let ts = take n $ generateRandomTreesProb opts gen cgr probs cat
    returnArg (ATrms (map (term2tree gro) ts)) sa

  CGenerateRandom -> do
    let 
      a' = case a of
        ASTrm _ -> s2t a
        AString _ -> s2t a
        _ -> a
    case a' of
      ATrms (trm:_) -> case tree2exp trm of
        G.EInt _ -> do
          putStrLn "Warning: Number argument deprecated, use gr -number=n instead"
          ts <- randomTreesIO opts gro (optIntOrN opts flagNumber 1)
          returnArg (ATrms ts) sa
        _ -> do
            g    <- newStdGen
            case (goFirstMeta (tree2loc trm) >>= refineRandom g 41 cgr) of
              Ok trm' -> returnArg (ATrms [loc2tree trm']) sa
              Bad s   -> returnArg (AError s) sa
      _ -> do
        ts <- randomTreesIO opts gro (optIntOrN opts flagNumber 1)
        returnArg (ATrms ts) sa
  CGenerateTrees -> do
    let 
      a' = case a of
        ASTrm _ -> s2t a
        AString _ -> s2t a
        _ -> a
      mt = case a' of
        ATrms (tr:_) -> Just tr
        _ -> Nothing
    returnArg (ATrms $ generateTrees opts gro mt) sa

  CShowTreeGraph | oElem emitCode opts -> do -- -o
    returnArg (AString $ visualizeTrees opts $ strees $ s2t a) sa
  CShowTreeGraph  -> do
    let g0 = writeFile "grphtmp.dot" $ visualizeTrees opts $ strees $ s2t a
        g1 = system "dot -Tps grphtmp.dot >grphtmp.ps" 
        g2 = system "gv grphtmp.ps &" 
        g3 = return () ---- system "rm -f grphtmp.*"
    justOutput opts (g0 >> g1 >> g2 >> g3 >> return ()) sa

  CPutTerm -> changeArg (opTT2CommandArg (optTermCommand opts gro)  . s2t) sa

  CWrapTerm f -> changeArg (opTT2CommandArg (return . wrapByFun opts gro f)) sa
  CMorphoAnalyse -> changeArg (AString . morphoAnalyse opts gro . prCommandArg) sa
  CTestTokenizer -> changeArg (AString . optTokenizer opts gro . prCommandArg) sa

  CComputeConcrete t -> do
    m <- return $
         maybe (I.identC "?") id $  -- meaningful if no opers in t 
           maybe (resourceOfShellState st) (return . I.identC) $ -- topmost res
             getOptVal opts useResource             -- flag -res=m
    justOutput opts (putStrLn (err id (prt . stripTerm) (
                string2srcTerm src m t >>= 
                Ch.justCheckLTerm src  >>=
                Co.computeConcrete src))) sa
---                Co.computeConcreteRec src))) sa
  CShowOpers t -> do
    m <- return $
         maybe (I.identC "?") id $  -- meaningful if no opers in t 
           maybe (resourceOfShellState st) (return . I.identC) $ -- topmost res
             getOptVal opts useResource             -- flag -res=m
    justOutput opts (putStrLn (err id (unlines . map prOperSignature) (
                string2srcTerm src m t >>= (\t' -> 
                Co.computeConcrete src t' >>=  (\v -> 
                return (L.opersForType src t' v)))))) sa


  CTranslationQuiz il ol -> do
    warnDiscont opts 
    justOutput opts (teachTranslation opts (sgr il) (sgr ol)) sa
  CTranslationList il ol -> do
    warnDiscont opts
    let n = optIntOrN opts flagNumber 10 
    qs <- transTrainList opts (sgr il) (sgr ol) (toInteger n)
    returnArg (AString $ foldr (+++++) [] [unlines (s:ss) | (s,ss) <- qs]) sa

  CMorphoQuiz -> do
    warnDiscont opts 
    justOutput opts (teachMorpho opts gro) sa
  CMorphoList -> do
    let n = optIntOrN opts flagNumber 10  
    warnDiscont opts 
    qs <- useIOE [] $ morphoTrainList opts gro (toInteger n)
    returnArg (AString $ foldr (+++++) [] [unlines (s:ss) | (s,ss) <- qs]) sa

  CReadFile file   -> returnArgIO   (readFileIf file >>= return . AString) sa
  CWriteFile file  -> justOutputArg opts (writeFile file) sa
  CAppendFile file -> justOutputArg opts (appendFile file) sa
  CSpeakAloud      -> justOutputArg opts (speechGenerate opts) sa
  CSystemCommand s -> case a of
    AUnit -> justOutput opts (system s >> return ()) sa
    _     -> systemArg  opts a s sa 
  CPutString       -> changeArg    (opSS2CommandArg (optStringCommand opts gro)) sa
-----  CShowTerm        -> changeArg  (opTS2CommandArg (optPrintTerm opts gro) . s2t) sa
  CGrep ms -> changeArg (AString . unlines . filter (grep ms) . lines . prCommandArg) sa


  CSetFlag           -> changeState (addGlobalOptions opts0) sa
---- deprec!  CSetLocalFlag lang -> changeState (addLocalOptions lang opts0) sa

  CHelp (Just c) -> returnArg   (AString (txtHelpCommand c)) sa
  CHelp _ -> case opts0 of
    Opts [o] | o == showAll  -> returnArg   (AString txtHelpFile) sa
    Opts [o] | o == showDefs -> returnArg   (AString (txtHelpMacros sh)) sa
    Opts [o]        -> returnArg   (AString (txtHelpCommand ('-':prOpt o))) sa
    _               -> returnArg   (AString txtHelpFileSummary) sa

  CPrintGrammar       -> returnArg (AString (optPrintGrammar opts gro)) sa
  CPrintGlobalOptions -> justOutput opts (putStrLn  $ prShellStateInfo st) sa
  CPrintInformation c -> justOutput opts (useIOE () $ showInformation opts st c) sa
  CPrintLanguages     -> justOutput opts 
                         (putStrLn $ unwords $ map prLanguage $ allLanguages st) sa
  CPrintMultiGrammar  -> do
    sa' <- changeState purgeShellState sa
    returnArg (AString (optPrintMultiGrammar opts cgr)) sa'
  CShowGrammarGraph  -> do
    ---- sa' <- changeState purgeShellState sa
    let g0 = writeFile "grphtmp.dot" $ visualizeCanonGrammar opts cgr
        g1 = system "dot -Tps grphtmp.dot >grphtmp.ps" 
        g2 = system "gv grphtmp.ps &" 
        g3 = return () ---- system "rm -f grphtmp.*"
    justOutput opts (g0 >> g1 >> g2 >> g3 >> return ()) sa
  CPrintSourceGrammar -> 
      returnArg (AString (visualizeSourceGrammar src)) sa

----  CPrintGramlet       -> returnArg (AString (Gr.prGramlet st)) sa
----  CPrintCanonXML      -> returnArg (AString (Canon.prCanonXML st False)) sa
----  CPrintCanonXMLStruct -> returnArg (AString (Canon.prCanonXML st True)) sa
  _ -> justOutput opts (putStrLn "command not understood") sa

 where
   sgr = stateGrammarOfLang st
   gro = grammarOfOptState opts st
   opts = addOptions opts0 (globalOptions st)
   src = srcModules st
   cgr = canModules st

   s2t a = case a of
     ASTrm ('$':c) -> maybe (AError "undefined term") (ATrms . return) $ lookupShTerm sh c
     ASTrm s  -> err AError (ATrms . return) $ string2treeErr gro s
     AString s  -> err AError (ATrms . return) $ string2treeErr gro s
     _ -> a

   strees a = case a of
     ATrms ts -> ts
     _ -> [] 

   warnDiscont os = err putStrLn id $ do
     let c0 = firstAbsCat os gro
     c <- GrammarToCanon.redQIdent c0
     lang <- maybeErr "no concrete" $ languageOfOptState os st
     t <- return $ errVal CMacros.defLinType $ Look.lookupLincat cgr $ CMacros.redirectIdent lang c
     return $ if CMacros.isDiscontinuousCType t 
       then (putStrLn ("Warning: discontinuous category" +++ prt_ c))
       else (return ())

   grep ms s = (if oElem invertGrep opts then not else id) $ grepv ms s --- -v
   grepv ms s = case s of
     _:cs -> isPrefixOf ms s || grepv ms cs
     _ -> isPrefixOf ms s

-- commands either change the state or process the argument, but not both
-- some commands just do output

changeState :: ShellStateOper -> ShellIO
changeState f ((st,h),a) = return ((f st,h), a)

changeStateErr :: ShellStateOperErr -> ShellIO
changeStateErr f ((st,h),a) = case f st of
  Ok st' -> return ((st',h), a)
  Bad s  -> return ((st, h),AError s)

changeArg :: (CommandArg -> CommandArg) -> ShellIO
changeArg f (st,a) = return (st, f a)

changeArgMsg :: (CommandArg -> (CommandArg,String)) -> ShellIO
changeArgMsg f (st,a) = do
  let (b,msg) = f a
  putStrLnFlush msg
  return (st, b)

returnArg :: CommandArg -> ShellIO
returnArg = changeArg . const

returnArgIO :: IO CommandArg -> ShellIO
returnArgIO io (st,_) = io >>= (\a -> return (st,a))

justOutputArg :: Options -> (String -> IO ()) -> ShellIO
justOutputArg opts f sa@(st,a) = f (utf (prCommandArg a)) >> return (st, AUnit) 
 where
   utf = if (oElem useUTF8 opts) then encodeUTF8 else id

justOutput :: Options -> IO () -> ShellIO
justOutput opts = justOutputArg opts . const

systemArg :: Options -> CommandArg -> String -> ShellIO
systemArg _ cont syst sa = do
  writeFile tmpi $ prCommandArg cont
  system $ syst ++ " <" ++ tmpi ++ " >" ++ tmpo 
  s <- readFile tmpo
  returnArg (AString s) sa
 where
   tmpi = "_tmpi" ---
   tmpo = "_tmpo"

-- | type system for command arguments; instead of plain strings...
data CommandArg = 
   AError  String 
 | ATrms   [Tree] 
 | ASTrm   String   -- ^ to receive from parser 
 | AStrs   [Str] 
 | AString String 
 | AUnit 
  deriving (Eq, Show)

prCommandArg :: CommandArg -> String
prCommandArg arg = case arg of
  AError s   -> s
  AStrs ss   -> sstrV ss
  AString s  -> s
  ATrms []   -> "no tree found"
  ATrms tt   -> unlines $ map prt_Tree tt
  ASTrm s    -> s
  AUnit      -> ""

opSS2CommandArg :: (String -> String) -> CommandArg -> CommandArg
opSS2CommandArg f = AString . f . prCommandArg

opST2CommandArg :: (String -> Err [Tree]) -> CommandArg -> CommandArg
opST2CommandArg f = err AError ATrms . f . prCommandArg

opTS2CommandArg :: (Tree -> String) -> CommandArg -> CommandArg
opTS2CommandArg f (ATrms ts) = AString $ unlines $ map f ts
opTS2CommandArg _ (AError s) = AError ("expected term, but got error:" ++++ s)
opTS2CommandArg _ a = AError ("expected term, but got:" ++++ prCommandArg a)

opTT2CommandArg :: (Tree -> [Tree]) -> CommandArg -> CommandArg
opTT2CommandArg f (ATrms ts) = ATrms $ concat $ map f ts
opTT2CommandArg _ (AError s) = AError ("expected term, but got error:" ++++ s)
opTT2CommandArg _ a = AError ("expected term, but got:" ++++ prCommandArg a)