packages feed

forml-0.2: src/hs/Main.hs

{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE NamedFieldPuns       #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE QuasiQuotes          #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE RecordWildCards      #-}
{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE TupleSections        #-}
{-# LANGUAGE ViewPatterns         #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE OverloadedStrings    #-}

module Main(main) where

import Text.InterpolatedString.Perl6

import Control.Concurrent
import Control.Monad.State hiding (lift)

import System.Directory
import System.Environment
import System.IO
import System.IO.Unsafe
import System.Log.Logger
import System.Log.Handler.Syslog

import Data.String.Utils (split)
import Data.List as L
import qualified Data.Serialize as S
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL

import GHC.Generics

import           Forml.CLI
import           Forml.Closure
import           Forml.Doc
import           Forml.Javascript
import           Forml.Javascript.Backend
import           Forml.Javascript.Test
import           Forml.Javascript.Utils   (prelude)
import qualified Forml.Optimize           as O
import           Forml.Parser
import           Forml.Static
import           Forml.TypeCheck

import qualified Codec.Compression.GZip as G

to_parsed :: Title -> Source -> TypeSystem -> Either [Error] (TypeSystem, Program)
to_parsed name src env = case parseForml name src of
                              Left x  -> Left [show x]
                              Right x -> case tiProgram x env of
                                           (as, []) -> Right (as, x)
                                           (_, y)   -> Left y

to_filename = head . split "." . last . split "/"

data Compiled = Compiled { filename :: Filename
                         , types    :: TypeSystem
                         , program  :: Program
                         , source   :: Source
                         , title    :: Title
                         , js       :: String
                         , opt_st   :: O.OptimizeState
                         , tests    :: String } deriving (Generic)

instance S.Serialize Compiled

parse_forml :: [Filename] -> Compiled -> Runner (TypeSystem, Program) -> IO [Compiled]
parse_forml filenames compiled runner =

    do sources <- mapM get_source filenames
       foldM parse'
             [compiled]
             (sources `zip` filenames)

    where parse' :: [Compiled]
                 -> (Source, Filename)
                 -> IO [Compiled]

          parse' acc (src'', filename) = do

              let Compiled { types = ts, opt_st = opt } = last acc
              let (title, src) = get_title (to_filename filename) src''
              let src'         = to_literate filename . (++ "\n") $ src

              (ts', ast) <- runner [qq|Loading {filename}|] $ return $ to_parsed filename src' ts

              let (opt', opt_ast) = O.run_optimizer ast (opt { O.assumptions = ts'})
              let (js',  tests')  = gen_js src' (opt_ast) (whole_program $ map program acc ++ [opt_ast])

              return $ acc ++ [Compiled (to_filename filename) ts' opt_ast src' title js' opt' tests']

          get_source filename =
             do hFile <- openFile filename ReadMode
                hGetContents hFile

          whole_program p = Program $ get_program p
          
          get_program (Program ss: ps) = ss ++ get_program ps
          get_program [] = []

gen_js :: Source -> Program -> Program -> (String, String)
gen_js src p whole_program = (g, h)

    where g = unserialize $ render whole_program src p
          h = unserialize $ render_spec whole_program src p

          unserialize x = compress $ read' x

read' xs @ ('"':_) = read xs
read' x = x

main :: IO ()
main  = do  args <- getArgs
            if silent $ parseArgs args 
              then updateGlobalLogger "Global" (setLevel ERROR)
              else updateGlobalLogger "Global" (setLevel INFO)
            main' $ parseArgs args

main' :: RunConfig -> IO ()
main' 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  "

          runner = if silent rc' then run_silent else monitor

          watch' rc =
              do  x <- mapM getModificationTime . inputs $ rc
                  compile rc
                  infoM "Global" "Waiting ..."
                  wait rc x

          wait rc x =
              do threadDelay 1000
                 x' <- mapM getModificationTime . inputs $ rc
                 if x /= x' then do infoM "Global" "\r"
                                    watch' rc
                            else wait rc x

          compile rc =
              let empty_state =
                      Compiled "" [] (Program []) "" "" [] (O.gen_state []) [] in

              do state <- if implicit_prelude rc
                          then return $ case S.decode prelude' of
                              Left x -> error x
                              Right x -> x
                          else return $ empty_state

                 compiled <- drop 1 `fmap` parse_forml (inputs rc) state runner
                  
                 _ <- mapM (\ c @ (Compiled { .. }) ->
                                runner [qq|Compiling {filename}.obj |] $ fmap Right $
                                B.writeFile (filename ++ ".obj") $ B.concat $ BL.toChunks $ G.compress $ BL.fromChunks [S.encode c])
                           compiled

                 let js'' = read' prelude ++ "\n"
                               ++ if implicit_prelude rc then js state ++ (concatMap js compiled) else concatMap js compiled

                 js' <- case rc of
                              RunConfig { optimize = True } ->
                                runner [qq|Closure {output rc}.js |] $ closure_local js'' "ADVANCED_OPTIMIZATIONS"
                              RunConfig { silent = False } ->
                                do warn "Closure [libs]" js''
                              _ -> do return js''

                 tests' <- case rc of
                              RunConfig { optimize = True } ->
                                  zipWithM (\title t -> runner [qq|Closure {title}.spec.js |] $ closure_local t "SIMPLE_OPTIMIZATIONS")
                                      (map filename compiled)
                                      (map ((read' prelude ++) . tests) compiled)
                              RunConfig { silent = False } ->
                                  warn "Closure [tests]" (map tests compiled)
                              _ -> do return (map tests compiled)

                 if flush rc 
                    then putStr js' >> hFlush stdout
                    else writeFile (output rc ++ ".js") js'
                 
                 _ <- zipWithM writeFile (map (++ ".spec.js") (map filename compiled)) tests'

                 if write_docs rc
                     then docs js'
                              tests'
                              (map filename compiled)
                              (map title compiled)
                              (map program compiled)
                              (map source compiled)
                     else runner "Docs" $ return $ Right ()

                 _ <- sequence (zipWith (test rc js') (map filename compiled) tests')

                 if (show_types rc)
                      then putStrLn $ ("\nTypes\n\n  " ++ concatMap (concatMap f . types) compiled)
                      else return ()