{-# LANGUAGE FlexibleContexts, NamedFieldPuns, OverlappingInstances,
QuasiQuotes, RankNTypes, RecordWildCards, TemplateHaskell,
TupleSections, ViewPatterns #-}
module Main(main) where
import Text.InterpolatedString.Perl6
import Control.Applicative
import Control.Monad.State hiding (lift)
import System.Directory
import System.Environment
import System.Exit
import System.IO
import System.Process
import Control.Concurrent
import Text.Pandoc
import Data.Char (isAscii, ord)
import Data.List as L
import Data.Monoid
import Data.String.Utils
import Formal.Closure
import Formal.Javascript
import Formal.Javascript.Backend
import Formal.Javascript.Utils (prelude)
import Formal.Parser
import Formal.TypeCheck hiding (split)
import Formal.Types.Statement
import Formal.CLI
import qualified Formal.Optimize as O
import qualified Data.ByteString.Char8 as B
import Data.FileEmbed
toEntities :: String -> String
toEntities [] = ""
toEntities (c:cs) | isAscii c = c : toEntities cs
| otherwise = [qq|{ord c};{toEntities cs}|]
toHTML :: String -> String
toHTML = toEntities . writeHtmlString defaultWriterOptions . readMarkdown defaultParserState
to_literate :: String -> String -> String
to_literate filename
| (head . tail . split "." $ filename) == "formal" = unlines . map l . lines
| otherwise = id
where l (lstrip -> '-':'-':xs) = lstrip xs
l x = " " ++ x
to_parsed :: String -> String -> TypeSystem -> Either [String] (TypeSystem, (Program, String))
to_parsed name src env = case parseFormal name src of
Left x -> Left [show x]
Right x -> case tiProgram x env of
(as, []) -> Right (as, (x, src))
(_, y) -> Left y
parse_formal :: [String] -> IO ([String], TypeSystem, [(Program, String)])
parse_formal xs = foldM parse' (xs, [], []) xs
where parse' :: ([String], TypeSystem, [(Program, String)]) -> String -> IO ([String], TypeSystem, [(Program, String)])
parse' (zs, ts, as) filename = do hFile <- openFile filename ReadMode
src <- hGetContents hFile
let src' = to_literate filename . (++ "\n") $ src
(ts', as') <- monitor [qq|Loading $filename|] $ return$ to_parsed (head zs) src' ts
return (tail zs, ts ++ ts', as ++ [as'])
gen_js :: [String] -> [Program] -> (String, [(String, String)])
gen_js src p = (compress (read' prelude ++ "\n" ++ (unlines $ map read' $ zipWith (render (Program $ get_program p)) src p)), [("", read' prelude ++ "\n" ++ (unlines $ map read' $ zipWith (render_spec (Program $ get_program p)) src p))])
read' :: [Char] -> [Char]
read' xs @ ('"':_) = read xs
read' x = x
get_program :: [Program] -> [Statement]
get_program (Program ss: ps) = ss ++ get_program ps
get_program [] = []
main :: IO ()
main = do args <- getArgs
main' args
main' :: [String] -> IO ()
main' (parseArgs -> rc') =
if watch rc'
then watch' rc'
else compile rc'
where f (x, y) = show x ++ "\n " ++ concat (L.intersperse "\n " (map show y)) ++ "\n\n "
jquery = $(embedFile "lib/js/jquery.js")
header = $(embedFile "src/html/header.html")
footer = $(embedFile "src/html/footer.html")
jasmine = $(embedFile "lib/js/jasmine-1.0.1/jasmine.js")
`mappend` $(embedFile "lib/js/jasmine-1.0.1/jasmine-html.js")
console = "prelude.html.console_runner()"
report = $(embedFile "src/js/FormalReporter.js")
htmljs = "$('#run_tests').click(prelude.html.table_of_contents)"
watch' rc =
do x <- mapM getModificationTime . inputs $ rc
compile rc
putStr "Waiting ..."
hFlush stdout
wait rc x
wait rc x =
do threadDelay 1000
x' <- mapM getModificationTime . inputs $ rc
if x /= x' then do putStr "\r"
watch' rc
else wait rc x
compile rc =
do (_, as, src') <- parse_formal$ inputs rc
let src'' = O.run_optimizer (fmap fst src') as
let (js', ((_, tests'):_)) = gen_js (fmap snd src') src''
js <- if optimize rc
then (monitor "Closure [libs"$ closure_local (js') "ADVANCED_OPTIMIZATIONS")
else do warn "Closure [libs]" js'
tests <- case rc of
RunConfig { optimize = True, run_tests = Phantom } ->
monitor "Closure [tests"$ closure_local (tests') "SIMPLE_OPTIMIZATIONS"
_ -> warn "Closure [tests]" tests'
writeFile (output rc ++ ".js") js
writeFile (output rc ++ ".spec.js") tests
monitor "Docs" $
if write_docs rc
then let xxx = map fst src'
yyy = map snd src'
html' xxx' yyy' = highlight (case xxx' of (Program xs) -> get_tests xs)$ toHTML (annotate_tests yyy' xxx')
html = concat $ zipWith html' xxx yyy
prelude' = ""
hook = ""
in do writeFile ((output rc) ++ ".html") (B.unpack header ++ prelude' ++ html ++ hook ++ B.unpack footer)
return $ Right ()
else return $ Right ()
case run_tests rc of
Node ->
monitor "Testing [Node.js]"$
do (Just std_in, Just std_out, _, p) <-
createProcess (proc "node" []) { std_in = CreatePipe, std_out = CreatePipe }
forkIO $ do errors <- hGetContents std_out
putStr errors
hFlush stdout
hPutStrLn std_in$ B.unpack jasmine
hPutStrLn std_in$ js ++ "\n\n"
hPutStrLn std_in$ tests
hPutStrLn std_in$ console
z <- waitForProcess p
case z of
ExitFailure _ -> return$ Left []
ExitSuccess -> if (show_types rc)
then Right <$> putStrLn ("\nTypes\n\n " ++ concat (map f as))
else return$ Right ()
Phantom ->
monitor "Testing [Phantom.js]"$
do writeFile (output rc ++ ".phantom.js")
(B.unpack jquery ++ B.unpack jasmine ++ js ++ tests ++ console)
(Just std_in, Just std_out, _, p) <-
createProcess (proc "phantomjs" [output rc ++ ".phantom.js"]) { std_in = CreatePipe, std_out = CreatePipe }
forkIO $ do errors <- hGetContents std_out
putStr errors
hFlush stdout
z <- waitForProcess p
system$ "rm " ++ output rc ++ ".phantom.js"
case z of
ExitFailure _ -> return$ Left []
ExitSuccess -> if (show_types rc)
then Right <$> putStrLn ("\nTypes\n\n " ++ concat (map f as))
else return$ Right ()
NoTest ->
do warn "Testing" ()
if (show_types rc)
then putStrLn$ "\nTypes\n\n " ++ concat (map f as)
else return ()