root/src/Pugs.hs

Revision 21936, 16.8 kB (checked in by gaal, 5 months ago)

* Make stdout line-buffered by default. I'll put off doing this

for opened files until an interface is added to change the
buffering mode (although flush already exists).

Incredibly, pugs has been NoBuffering? stdout since r1!

  • Property svn:mime-type set to text/plain; charset=UTF-8
  • Property svn:eol-style set to native
Line 
1{-# OPTIONS_GHC -fglasgow-exts -fallow-overlapping-instances -fffi #-}
2
3{-|
4    Public API for the Pugs system.
5
6>   Dance all ye joyful, now dance all together!
7>   Soft is the grass, and let foot be like feather!
8>   The river is silver, the shadows are fleeting;
9>   Merry is May-time, and merry our meeting.
10
11-}
12
13module Pugs (
14    module Pugs,
15    Command(..),
16    banner,
17    liftSTM,
18    printCommandLineHelp,
19    intro,
20    initializeShell,
21    getCommand,
22    pretty,
23    printInteractiveHelp,
24) where
25import Pugs.AST
26import Pugs.CodeGen
27import Pugs.Config
28import Pugs.Embed
29import Pugs.Eval
30import Pugs.External
31import Pugs.Help
32import Pugs.Internals
33import Pugs.Monads
34import Pugs.Parser.Program
35import Pugs.Pretty
36import Pugs.Run
37import Pugs.Shell
38import Pugs.Types
39import Data.IORef
40import qualified Data.Map as Map
41import qualified System.FilePath as FilePath (combine, splitFileName)
42import Control.Timeout
43
44{-|
45The entry point of Pugs. Uses 'Pugs.Run.runWithArgs' to normalise the command-line
46arguments and pass them to 'run'.
47-}
48pugsMain :: IO ()
49pugsMain = do
50    let ?debugInfo = Nothing
51    timeout <- getEnv "PUGS_TIMEOUT"
52    case timeout of
53        Just str | [(t, _)] <- reads str -> do
54            addTimeout t (hPutStrLn stderr "*** TIMEOUT" >> _exit 1)
55            return ()
56        _ -> return ()
57    mainWith run
58
59foreign import ccall unsafe _exit :: Int -> IO ()
60
61defaultProgramName :: String
62defaultProgramName = "<interactive>"
63
64runFile :: String -> IO ()
65runFile file = do
66    withArgs [file] pugsMain
67
68run :: [String] -> IO ()
69run xs = let ?debugInfo = Nothing in run' xs
70
71-- see also Run/Args.hs
72run' :: (?debugInfo :: DebugInfo) => [String] -> IO ()
73run' ("-d":rest)                 = do
74    info <- newDebugInfo
75    let ?debugInfo = info
76    run' rest
77run' ("-l":rest)                 = run' rest
78run' ("-w":rest)                 = run' rest
79run' ("-I":_:rest)               = run' rest
80
81-- XXX should raise an error here:
82-- run ("-I":[])                     = do
83--                                    print "Empty -I"
84
85run' ("-h":_)                  = printCommandLineHelp
86run' ("-V":_)                  = printConfigInfo []
87run' ("-V:":item:_)            = printConfigInfo [item]
88run' ("-v":_)                  = banner
89
90-- turn :file: and "-e":frag into a common subroutine/token
91run' ("-c":"-e":prog:_)          = doCheck "-e" prog
92run' ("-c":file:_)               = readFile file >>= doCheck file
93
94-- -CPIL1.Perl5 outputs PIL formatted as Perl 5.
95run' ("-C":backend:args) | (== map toLower backend) `any` ["js","perl5","js-perl5"] = do
96    exec <- getArg0
97    doHelperRun backend ("--compile-only":("--pugs="++exec):args)
98run' ("-C":backend:"-e":prog:_)           = doCompileDump backend "-e" prog
99run' ("-C":backend:file:_)                = readFile file >>= doCompileDump backend file
100
101run' ("-B":backend:_) | (== map toLower backend) `any` ["js","perl5","js-perl5","redsix"] = do
102    exec <- getArg0
103    args <- getArgs
104    doHelperRun backend (("--pugs="++exec):args)
105run' ("-B":backend:"-e":prog:_)           = doCompileRun backend "-e" prog
106run' ("-B":backend:file:_)                = readFile file >>= doCompileRun backend file
107
108run' ("--external":mod:"-e":prog:_)       = doExternal mod "-e" prog
109run' ("--external":mod:file:_)            = readFile file >>= doExternal mod file
110
111run' ("-e":prog:args)                     = do doRun "-e" args prog
112-- -E is like -e, but not accessible as a normal parameter and used only
113-- internally:
114--   "-e foo bar.pl" executes "foo" with @*ARGS[0] eq "bar.pl",
115--   "-E foo bar.pl" executes "foo" and then bar.pl.
116-- XXX - Wrong -- Need to preserve environment across -E runs
117run' ("-E":prog:rest)            = run' ("-e":prog:[]) >> run' rest
118run' ("-":args)                  = do doRun "-" args =<< readStdin
119run' (file:args)                 = readFile file >>= doRun file args
120run' []                          = do
121    isTTY <- hIsTerminalDevice stdin
122    if isTTY
123        then do banner >> intro >> repLoop
124        else run' ["-"]
125
126readStdin :: IO String
127readStdin = do
128    eof     <- isEOF
129    if eof then return [] else do
130    ch      <- getChar
131    rest    <- readStdin
132    return (ch:rest)
133
134repLoop :: IO ()
135repLoop = initializeShell $ do
136    tvEnv <- io . newTVarIO . noEnvDebug =<< io (tabulaRasa defaultProgramName)
137    fix $ \loop -> do
138        command <- getCommand
139        let parseEnv f prog = do
140                env <- stm (readTVar tvEnv)
141                doParse env f defaultProgramName prog
142            resetEnv = do
143                env <- fmap noEnvDebug (tabulaRasa defaultProgramName)
144                stm (writeTVar tvEnv env)
145        if command == CmdQuit then io $ putStrLn "Leaving pugs." else do
146            io $ case command of
147                CmdLoad fn        -> doLoad tvEnv fn
148                CmdRun opts prog  -> doRunSingle tvEnv opts prog
149                CmdParse prog     -> parseEnv pretty prog
150                CmdParseRaw prog  -> parseEnv show prog
151                CmdHelp           -> printInteractiveHelp
152                CmdReset          -> resetEnv
153                _                 -> return ()
154            loop
155
156mainWith :: ([String] -> IO a) -> IO ()
157mainWith run = do
158    hSetBuffering stdout LineBuffering
159--    when (isJust _DoCompile) $ do
160--        writeIORef (fromJust _DoCompile) doCompile
161    runWithArgs run
162    globalFinalize
163
164-- convenience functions for GHCi
165eval :: String -> IO ()
166eval prog = do
167    args <- getArgs
168    runProgramWith id (putStrLn . encodeUTF8 . pretty) defaultProgramName args (encodeUTF8 prog)
169
170parse :: String -> IO ()
171parse prog = do
172    env <- tabulaRasa defaultProgramName
173    doParse env (encodeUTF8 . pretty) "-" (encodeUTF8 prog)
174
175dump :: String -> IO ()
176dump = (doParseWith $ \env _ -> print $ envBody env) "-"
177
178globalFinalize :: IO ()
179globalFinalize = join $ readIORef _GlobalFinalizer
180
181dumpGlob :: String -> IO ()
182dumpGlob = (doParseWith $ \env _ -> do
183    glob <- stm . readMPad $ envGlobal env
184    print $ filterUserDefinedPad glob) "-"
185
186{-|
187Create a \'blank\' 'Env' for our program to execute in. Of course,
188'prepareEnv' actually declares quite a few symbols in the environment,
189e.g. \'\@\*ARGS\', \'\$\*PID\', \'\$\*ERR\' etc.
190
191('Tabula rasa' is Latin for 'a blank slate'.)
192-}
193tabulaRasa :: String -> IO Env
194tabulaRasa name = prepareEnv name []
195
196doCheck :: FilePath -> String -> IO ()
197doCheck = doParseWith $ \_ name -> do
198    putStrLn $ name ++ " syntax OK"
199
200doExternal :: String -> FilePath -> String -> IO ()
201doExternal mod = doParseWith $ \env _ -> do
202    str <- externalize mod $ envBody env
203    putStrLn str
204
205doCompile :: String -> FilePath -> String -> IO String
206doCompile backend = doParseWith $ \env file -> do
207    globRef <- stm $ do
208        glob <- readMPad $ envGlobal env
209        newMPad $ filterUserDefinedPad glob
210    codeGen backend file env{ envGlobal = globRef }
211
212initCompile :: IO ()
213initCompile = do
214    compPrelude <- getEnv "PUGS_COMPILE_PRELUDE"
215    let bypass = case compPrelude of
216            Nothing     -> True
217            Just ""     -> True
218            Just "0"    -> True
219            _           -> False
220    setEnv "PUGS_COMPILE_PRELUDE" (if bypass then "0" else "") True
221
222doCompileDump :: String -> FilePath -> String -> IO ()
223doCompileDump backend file prog = do
224    initCompile
225    str <- doCompile backend' file prog
226    putStr str
227    where
228    backend' = capitalizeWord backend
229    capitalizeWord []     = []
230    capitalizeWord (c:cs) = toUpper c:(map toLower cs)
231
232doCompileRun :: String -> FilePath -> String -> IO ()
233doCompileRun backend file prog = do
234    initCompile
235    str <- doCompile backend' file prog
236    evalEmbedded backend' str
237    where
238    backend' = capitalizeWord backend
239    capitalizeWord []     = []
240    capitalizeWord (c:cs) = toUpper c:(map toLower cs)
241
242doHelperRun :: String -> [String] -> IO ()
243doHelperRun backend args =
244    case map toLower backend of
245        "js"    -> if (args' == [])
246                   then (doExecuteHelper "jspugs.pl"  args)
247                   else (doExecuteHelper "runjs.pl"   args)
248        "perl5" ->       doExecuteHelper "v6.pm" args
249        "js-perl5" -> doExecuteHelper "runjs.pl" (jsPerl5Args ++ args)
250        "redsix" -> doExecuteHelper "redsix" args
251        _       ->       fail ("unknown backend: " ++ backend)
252    where
253    args' = f args
254    jsPerl5Args = words "--run=jspm --perl5"
255    f [] = []
256    f (bjs:rest)      | "-BJS" `isPrefixOf` map toUpper bjs = f rest
257    f ("-B":js:rest)  | "JS" `isPrefixOf` map toUpper  js = f rest
258    f (pugspath:rest) | "--pugs=" `isPrefixOf` pugspath = f rest
259    f (x:xs) = x:f xs
260
261doExecuteHelper :: FilePath -> [String] -> IO ()
262doExecuteHelper helper args = do
263    let searchPaths = concatMap (\x -> map (x++) suffixes) [["."], ["..", ".."], [getConfig "sourcedir"], [getConfig "sourcedir", "blib6", "pugs"], [getConfig "privlib", "auto", "pugs"], [getConfig "sitelib", "auto", "pugs"]]
264    mbin <- runMaybeT (findHelper searchPaths)
265    case mbin of
266        Just binary -> do
267            let (p, _) = FilePath.splitFileName binary
268            exitWith =<< executeFile' perl5 True (("-I" ++ p):binary:args) Nothing
269        _ -> fail ("Couldn't find helper program " ++ helper ++ " (searched in " ++ show (map (foldl1 FilePath.combine) searchPaths) ++ ")")
270    where
271    suffixes =
272        [ []
273        , ["perl5", "PIL2JS"]                -- sourcedir/perl5/PIL2JS/jspugs.pl
274        , ["perl5", "lib"]                   -- pugslibdir/perl5/lib/jspugs.pl
275        , ["misc", "pX", "Common", "redsix"] -- sourcedir/misc/pX/Common/redsix/redsix
276        ]
277    perl5 = getConfig "perl5_path"
278    findHelper :: [[FilePath]] -> MaybeT IO FilePath
279    findHelper []     = fail "Can't find anything"
280    findHelper (x:xs) = maybeFindFile file
281                `mplus` maybeFindFile (file ++ getConfig "exe_ext")
282                `mplus` findHelper xs
283        where
284        file = foldl1 FilePath.combine (x ++ [helper])
285    maybeFindFile :: FilePath -> MaybeT IO FilePath
286    maybeFindFile pathname = do
287        dir <- liftIO $ getDirectoryContents path `catchIO` (const $ return [])
288        guard (filename `elem` dir)
289        return pathname
290        where
291        (path, filename) = FilePath.splitFileName pathname
292
293doParseWith :: (Env -> FilePath -> IO a) -> FilePath -> String -> IO a
294doParseWith f name prog = do
295    env <- tabulaRasa name
296    f' $ parseProgram env{ envDebug = Nothing } name prog
297    where
298    f' env | Val err@(VError _ _) <- envBody env = do
299        hPutStrLn stderr $ pretty err
300        globalFinalize
301        exitFailure
302    f' env = f env name
303
304doParse :: Env -> (Exp -> String) -> FilePath -> String -> IO ()
305doParse env prettyFunc name prog = do
306    case envBody $ parseProgram env name prog of
307        (Val err@(VError _ _)) -> putStrLn $ pretty err
308        exp -> putStrLn $ prettyFunc exp
309
310doLoad :: TVar Env -> String -> IO ()
311doLoad env fn = do
312    runImperatively env (evaluate exp)
313    return ()
314    where
315    exp = App (_Var "&require") Nothing [Val $ VStr fn]
316
317doRunSingle :: TVar Env -> RunOptions -> String -> IO ()
318doRunSingle menv opts prog = (`catchIO` handler) $ do
319    exp     <- makeProper =<< parse
320    if exp == Noop then return () else do
321    env     <- theEnv
322    rv      <- runImperatively env (evaluate exp)
323    result  <- case rv of
324        VControl (ControlContinuation env' val _) -> do
325            stm $ writeTVar menv env'
326            return val
327        _ -> return rv
328    printer env result
329    where
330    parse = do
331        env <- stm $ readTVar menv
332        return $ envBody $ parseProgram env defaultProgramName $
333          (dropTrailingSemi prog)
334    dropTrailingSemi = reverse .
335                       (\x -> ';' : (dropWhile (`elem` " \t\r\n;") x)) .
336                       reverse
337    hasTrailingSemi = case f prog of ';':_ -> True; _ -> False
338        where f = dropWhile (`elem` " \t\r\n\f") . reverse
339    theEnv = do
340        ref <- if runOptSeparately opts
341                then (io . newTVarIO) =<< tabulaRasa defaultProgramName
342                else return menv
343        debug <- if runOptDebug opts
344                then newDebugInfo
345                else return Nothing
346        stm $ modifyTVar ref $ \e -> e{ envDebug = debug }
347        return ref
348    printer' = if runOptShowPretty opts then putStrLn . pretty else print
349    printer env = \val -> do
350      final <- runImperatively env (fromVal' val)
351      if hasTrailingSemi
352         then case final of (VError _ _) -> printer' final ; _ -> return ()
353         else printer' final
354    makeProper exp = case exp of
355        Val err@(VError (VStr msg) _)
356            | runOptShowPretty opts
357            , any (== "Unexpected end of input") (lines msg) -> do
358            cont <- readline "....> "
359            case cont of
360                Just line   -> do
361                    doRunSingle menv opts (prog ++ ('\n':line))
362                    return Noop
363                _           -> fail $ pretty err
364        Val err@VError{} -> fail $ pretty err
365        _ | runOptSeparately opts -> return exp
366        App (Syn "block" [Val (VCode cv)]) invs args -> return $
367            App (Syn "block" [Val (VCode cv{ subBody = makeDumpEnv (subBody cv) })]) invs args
368        _ -> return $ makeDumpEnv exp
369
370    -- XXX Generalize this into structural folding
371    makeDumpEnv Noop              = Syn "continuation" []
372    makeDumpEnv (Stmts x Noop)    = Stmts (Ann (Cxt cxtItemAny) x) (Syn "continuation" [])
373    makeDumpEnv (Stmts x exp)     = Stmts x   $ makeDumpEnv exp
374    makeDumpEnv (Ann ann exp)     = Ann ann   $ makeDumpEnv exp
375    makeDumpEnv (Sym x y z w exp) = Sym x y z w $ makeDumpEnv exp
376    makeDumpEnv exp               = Stmts (Ann (Cxt cxtItemAny) exp) (Syn "continuation" [])
377
378    handler (IOException ioe) | isUserError ioe = do
379        putStrLn "Internal error while running expression:"
380        putStrLn $ ioeGetErrorString ioe
381    handler err = do
382        putStrLn "Internal error while running expression:"
383        putStrLn $ show err
384
385runImperatively :: TVar Env -> Eval Val -> IO Val
386runImperatively menv eval = do
387    env <- stm $ readTVar menv
388    runEvalIO env $ do
389        val <- eval
390        newEnv <- ask
391        stm $ writeTVar menv newEnv
392        return val
393
394doRun :: (?debugInfo :: DebugInfo) => String -> [String] -> String -> IO ()
395doRun = do
396    runProgramWith (\e -> e{ envDebug = ?debugInfo }) end
397    where
398    end err@(VError _ _)  = do
399        hPutStrLn stderr $ encodeUTF8 $ pretty err
400        globalFinalize
401        exitFailure
402    end (VControl (ControlExit exit)) = do
403        globalFinalize
404        exitWith exit
405    end _ = return ()
406
407noEnvDebug :: Env -> Env
408noEnvDebug e = e{ envDebug = Nothing }
409
410runProgramWith ::
411    (Env -> Env) -> (Val -> IO a) -> VStr -> [VStr] -> String -> IO a
412runProgramWith fenv f name args prog = do
413    env <- prepareEnv name args
414    -- Cache the compilation tree right here.
415    -- We only really care about envGlobal and envBody here.
416    val <- runEnv $ parseProgram (fenv env) name prog
417    f val
418
419createConfigLine :: String -> String
420createConfigLine item = "\t" ++ item ++ ": " ++ (Map.findWithDefault "UNKNOWN" item config)
421
422printConfigInfo :: [String] -> IO ()
423printConfigInfo [] = do
424    libs <- getLibs
425    putStrLn $ unlines $
426        ["This is " ++ version ++ " built for " ++ getConfig "archname"
427        ,""
428        ,"Summary of pugs configuration:" ]
429        ++ map (\x -> createConfigLine x) (map (fst) (Map.toList config))
430        ++ [ "" ]
431        ++ [ "@*INC:" ] ++ libs
432
433printConfigInfo (item:_) = do
434        putStrLn $ createConfigLine item
435
436compPIR :: String -> IO ()
437compPIR prog = do
438    pir <- doCompile "PIR" "-" prog
439    putStr $ (subMain ++ (last $ split subMain pir))
440    where
441    subMain = ".sub main"
442
443runPIR :: String -> IO ()
444runPIR prog = do
445    pir <- doCompile "PIR" "-" prog
446    writeFile "a.pir" pir
447    fail "evalParrotFile is bitrotten."
448    -- evalParrotFile "a.pir"
449
450{-
451withInlinedIncludes :: String -> IO String
452withInlinedIncludes prog = do
453    libs <- getLibs
454    expandInc libs prog
455    where
456    expandInc :: [FilePath] -> String -> IO String
457    expandInc incs str = case breakOnGlue "\nuse " ('\n':str) of
458        Nothing -> case breakOnGlue "\nrequire " ('\n':str) of
459            Nothing -> return str
460            Just (pre, post) -> do
461                let (mod, (_:rest)) = span (/= ';') (dropWhile isSpace post)
462                mod'    <- includeInc incs mod
463                rest'   <- expandInc incs rest
464                return $ pre ++ mod' ++ rest'
465        Just (pre, post) -> do
466            let (mod, (_:rest)) = span isAlphaNum (dropWhile isSpace post)
467            mod'    <- includeInc incs mod
468            rest'   <- expandInc incs rest
469            return $ pre ++ "\n{" ++ mod' ++ "\n}\n" ++ rest'
470    includeInc :: [FilePath] -> String -> IO String
471    includeInc _ ('v':_) = return []
472    includeInc incs name = do
473        let name' = concat (intersperse "/" names) ++ ".pm"
474            names = split "::" name
475        pathName    <- requireInc incs name' (errMsg name incs)
476        readFile pathName
477    errMsg fn incs = "Can't locate " ++ fn ++ " in @*INC (@*INC contains: " ++ unwords incs ++ ")."
478-}
Note: See TracBrowser for help on using the browser.