-- Package: freesect-0.7
-- Description: Extend Haskell to support free sections
-- Example: zipWith (f __ b __ d) as cs
-- Author: Andrew Seniuk <rasfar@gmail.com>
-- Date: March 11, 2012
-- License: BSD3 (./LICENCE)
-- Executable: freesect
-- Usage: See accompanying files 000-readme and z
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-} -- needed for some of the type sigs.
{-# LANGUAGE CPP #-}
{- # LANGUAGE MultiParamTypeClasses #-}
{- # LANGUAGE RankNTypes #-} -- needed for the path accumulators.
{- # LANGUAGE ExistentialQuantification #-}
{- # LANGUAGE GADTs #-}
{- # LANGUAGE ScopedTypeVariables #-} -- needed for a pattern type sig.
-- CPP definitions are now set using compiler options; see ./z and ./ile.
-- #define ANNOTATED 0
-- #define PARALLEL 0
-- #define GHC_F 1
-- Most helpful sources:
-- - http://hpaste.org/steps/10722 -- use of everywhereM with State
-- - #haskell (thanks dreixel, quintessence, eyebloom, ...)
module Main(main) where
import System.Environment(getArgs)
import System.IO(writeFile,hFlush,stdout)
import Data.Data(Data)
import Data.Generics.Aliases(mkQ,mkT,mkM)
import Data.Generics.Schemes(everywhere,everywhereM,everything,gcount)
--import Data.Generics -- this suffices to import all the above
#if PARALLEL
import Control.Parallel.Strategies(rpar,parTraversable,runEval)
--import Control.Parallel -- Not imported with Strategies, but
-- contains only par and pseq.
--import Control.Concurrent
---import Control.Exception
---import System.IO.Unsafe
---import Foreign
#endif
import Control.Monad.State
import System.Random(StdGen,mkStdGen,next)
import Data.List(isPrefixOf)
#if ANNOTATED
import HSE.Annotated
import HSE.SrcLoc
#else
import HSE
#endif
import HSE.Extension
import FilesAndParsing
--------------------------------------------------------------------------------
#if ANNOTATED
fs_module :: Module SrcSpanInfo -> Module SrcSpanInfo
#else
fs_module :: Module -> Module
#endif
fs_module m0 = m4
where
m1 = everywhere (mkT step) m0
m2 = everywhere (mkT step2) m1
m3 = fs_guarded_rhss m2
m4 = fs_error_if_any_remain m3
#if ANNOTATED
step :: Rhs SrcSpanInfo -> Rhs SrcSpanInfo -- seems nec.
#else
-- step :: Rhs -> Rhs -- unnec.
#endif
#if ANNOTATED
step (UnGuardedRhs srcSpanInfo e) = UnGuardedRhs srcSpanInfo e'
#else
step (UnGuardedRhs e) = UnGuardedRhs e'
#endif
where e' = fs_rhs_exp fresh e
step x = x
#if ANNOTATED
step2 :: Rhs SrcSpanInfo -> Rhs SrcSpanInfo -- seems nec.
#else
-- step2 :: Rhs -> Rhs -- unnec.
#endif
#if ANNOTATED
step2 x@(UnGuardedRhs srcSpanInfo e)
| still_fsss = UnGuardedRhs srcSpanInfo e''
#else
step2 x@(UnGuardedRhs e)
| still_fsss = UnGuardedRhs e''
#endif
| otherwise = x
where
still_fsss = 0 < ( ( gcount (False `mkQ` p) x ) :: Int )
#if ANNOTATED
p :: Exp SrcSpanInfo -> Bool
p x@(FreeSectSlot _) = True
#else
-- p :: Exp -> Bool
p x@FreeSectSlot = True
#endif
p _ = False
e'' = fs_rhs_exp fresh e'
#if ANNOTATED
e' = Paren srcSpanInfo e
#else
e' = Paren e
#endif
step2 x = x
fresh = fs_fresh_name m0
#if ANNOTATED
fs_guarded_rhss :: Module SrcSpanInfo -> Module SrcSpanInfo
#else
fs_guarded_rhss :: Module -> Module
#endif
fs_guarded_rhss m = m''
where
m' = everywhere (mkT step) m
m'' = everywhere (mkT step2) m'
#if ANNOTATED
step :: GuardedRhs SrcSpanInfo -> GuardedRhs SrcSpanInfo -- seems nec.
#else
-- step :: GuardedRhs -> GuardedRhs -- unnec.
#endif
#if ANNOTATED
step (GuardedRhs srcSpanInfo slst e) = GuardedRhs srcSpanInfo slst e'
#else
step (GuardedRhs srcLoc slst e) = GuardedRhs srcLoc slst e'
#endif
where e' = fs_rhs_exp fresh e
step x = x
#if ANNOTATED
step2 :: GuardedRhs SrcSpanInfo -> GuardedRhs SrcSpanInfo -- seems nec.
#else
-- step2 :: GuardedRhs -> GuardedRhs -- unnec.
#endif
#if ANNOTATED
step2 x@(GuardedRhs srcSpanInfo slst e)
| still_fsss = GuardedRhs srcSpanInfo slst e''
#else
step2 x@(GuardedRhs srcLoc slst e)
| still_fsss = GuardedRhs srcLoc slst e''
#endif
| otherwise = x
where
still_fsss = 0 < ( ( gcount (False `mkQ` p) x ) :: Int )
#if ANNOTATED
p :: Exp SrcSpanInfo -> Bool
p x@(FreeSectSlot _) = True
#else
-- p :: Exp -> Bool
p x@FreeSectSlot = True
#endif
p _ = False
e'' = fs_rhs_exp fresh e'
#if ANNOTATED
e' = Paren srcSpanInfo e
#else
e' = Paren e
#endif
step2 x = x
fresh = fs_fresh_name m
#if ANNOTATED
fs_error_if_any_remain :: Module SrcSpanInfo -> Module SrcSpanInfo
#else
fs_error_if_any_remain :: Module -> Module
#endif
fs_error_if_any_remain m = m'
where
m' | still_fsss = error "Free sections can only occur in RHS Exp contexts."
| otherwise = m
still_fsss = 0 < ( ( gcount (False `mkQ` p) m ) :: Int )
#if ANNOTATED
p :: Exp SrcSpanInfo -> Bool
p x@(FreeSectSlot _) = True
p x@(FSContext _ _) = True
#else
-- p :: Exp -> Bool
p x@FreeSectSlot = True
p x@(FSContext _) = True
#endif
p _ = False
--------------------------------------------------------------------------------
-- v.0.0.5:
fs_rhs_exp :: Data a => String -> a -> a
--fs_rhs_exp fresh rhs = rhs'
fs_rhs_exp fresh rhs = rhs''
where
rhs' = everywhere (mkT step) rhs
rhs'' | num_fss_remaining > 0 = everywhere (mkT step2) rhs'
| otherwise = rhs'
#if ANNOTATED
step x@(FSContext srcSpanInfo e) = fs_lambda ps' x'
#else
step x@(FSContext e) = fs_lambda ps' x'
#endif
where (x',(ps,_)) = fs_name_slots fresh x
ps' = reverse ps
step x = x
num_fss_remaining = ( gcount (False `mkQ` p) rhs' ) :: Int
-- num_fss_remaining = ( error $ show $ ( gcount (False `mkQ` p) rhs' ) :: Int ) :: Int
#if ANNOTATED
p :: Exp SrcSpanInfo -> Bool
p x@(FreeSectSlot _) = True
#else
-- p :: Exp -> Bool
p x@FreeSectSlot = True
#endif
p _ = False
-- Default context inference works as follows:
-- The (semilattice) join of all unbracketed __'s in a RHS is found.
-- Then, the innermost enclosing Paren or infix $ determines the context,
-- or -- if neither exists -- the whole RHS is taken as context.
--
-- Would prefer to use everywhereBut or something, to stop
-- searching farther, but ... would need an "everywhereButM" I think,
-- since need to pass on the info that an amenable Paren
-- has already been found.
#if ANNOTATED
step2 :: Exp SrcSpanInfo -> Exp SrcSpanInfo
#else
-- step2 :: Exp -> Exp
#endif
#if ANNOTATED
-- | Paren l (Exp l)
step2 x@(Paren srcSpanInfo e)
| num_fss_remaining == gcount (False `mkQ` p) e
= x'_
| otherwise
= x
where
x_ = FSContext srcSpanInfo e
#else
step2 x@(Paren e)
| num_fss_remaining == gcount (False `mkQ` p) e
= x'_
| otherwise
= x
where
x_ = FSContext e
#endif
-- (We safely discarded the Paren from the AST since FSContext will
-- give Paren grouping behaviour in addition to freesect contexting.)
(x',(ps,_)) = fs_name_slots fresh x_
ps' = reverse ps
x'_ = fs_lambda ps' x'
#if ANNOTATED
-- | InfixApp l (Exp l) (QOp l) (Exp l)
step2 x@(InfixApp srcSpanInfo e1 qop e2)
| not good_op
= x
| num_fss_x < num_fss_remaining
= x
| num_fss_e2 == 0
= InfixApp srcSpanInfo e1'_ qop e2
| num_fss_e1 == 0
= InfixApp srcSpanInfo e1 qop e2'_
| otherwise
= x'_
where
e1_ = FSContext srcSpanInfo e1
e2_ = FSContext srcSpanInfo e2
x_ = FSContext srcSpanInfo x
-- May want to broaden this category? remember, it's a trade off,
-- if you use an op for a freesect context delimiter, it can't
-- be used inside a freesect with defaulting context.
-- To see why $ was chosen, check out the S23.hs test file.
good_op = case qop of
(QVarOp _ (UnQual _ (Symbol _ "$"))) -> True
_ -> False
#else
step2 x@(InfixApp e1 qop e2)
| not good_op
= x
| num_fss_x < num_fss_remaining
= x
| num_fss_e2 == 0
= InfixApp e1'_ qop e2
| num_fss_e1 == 0
= InfixApp e1 qop e2'_
| otherwise
= x'_
where
e1_ = FSContext e1
e2_ = FSContext e2
x_ = FSContext x
good_op = case qop of
(QVarOp (UnQual (Symbol "$"))) -> True
_ -> False
#endif
(e1',(ps1,_)) = fs_name_slots fresh e1_
ps1' = reverse ps1
(e2',(ps2,_)) = fs_name_slots fresh e2_
ps2' = reverse ps2
(x',(ps,_)) = fs_name_slots fresh x_
ps' = reverse ps
e1'_ = fs_lambda ps1' e1'
e2'_ = fs_lambda ps2' e2'
x'_ = fs_lambda ps' x'
num_fss_e1 = ( gcount (False `mkQ` p) e1 ) :: Int
num_fss_e2 = ( gcount (False `mkQ` p) e2 ) :: Int
num_fss_x = gcount (False `mkQ` p) x
step2 x = x
#if ANNOTATED
fs_lambda :: [String] -> Exp SrcSpanInfo -> Exp SrcSpanInfo -- seems nec.
#else
--fs_lambda :: [String] -> Exp -> Exp -- unnec.
#endif
fs_lambda ps_lambda e_lambda
#if ANNOTATED
| null ps_lambda = error $ "Error: Free section contains no wildcards.\n"
++ showSLorSSI ssi
#else
| null ps_lambda = error $ "Error: Free section contains no wildcards.\n"
++ "(Source location not available, try -annotated.)\n"
#endif
| otherwise = lambda
where
#if ANNOTATED
lambda = Lambda ssi ps_lambda' e_lambda''
ps_lambda' = map (\x->(PVar ssi (Ident ssi x))) ps_lambda
e_lambda'@(FSContext ssi e) = e_lambda
e_lambda'' = e
#else
lambda = Lambda srcloc ps_lambda' e_lambda''
ps_lambda' = map (\x->(PVar (Ident x))) ps_lambda
e_lambda'@(FSContext e) = e_lambda
e_lambda'' = e
srcloc = SrcLoc "" 0 0
#endif
#if ANNOTATED
showSLorSSI :: SrcSpanInfo -> String
showSLorSSI (SrcSpanInfo si _)
= fileName si ++ ": line=" ++ show (startLine si)
++ " col=" ++ show (startColumn si)
#else
showSLorSSI :: SrcLoc -> String
showSLorSSI sl@(SrcLoc n l c)
= n ++ ": line=" ++ show l ++ " col=" ++ show c
#endif
--------------------------------------------------------------------------------
-- Perhaps ironically, I don't like using partially-point-free function
-- declarations like this, but I couldn't figure out what to do with
-- the second parameter if I made it explicit!
-- We need to construct the fresh names in this recursion anyway, so
-- may as well collect them rather than recompute them in the caller,
-- although we could because they are canonically constructable from
-- fresh and n, the Int part of the state.
fs_name_slots :: Data a => String -> a -> (a,([String],Int))
fs_name_slots fresh
= flip runState ([],0) . everywhereM (mkM step)
where
#if ANNOTATED
step :: MonadState ([String],Int) m => Exp SrcSpanInfo -> m (Exp SrcSpanInfo) -- seems nec.
#else
-- step :: MonadState ([String],Int) m => Exp -> m Exp -- unnec.
#endif
#if ANNOTATED
step (FreeSectSlot srcSpanInfo)
#else
step FreeSectSlot
#endif
= do (ss,n) <- get
let s = fresh ++ show n
-- let s = "freeSect_" ++ show n
put ((s:ss),(1+n))
#if ANNOTATED
return $ ( Var srcSpanInfo ( UnQual srcSpanInfo ( Ident srcSpanInfo s ) ) )
#else
return $ Var $ UnQual $ Ident $ s
#endif
step x = return x
fs_all_identifiers :: Data a => a -> [String]
fs_all_identifiers = everything (++) ([] `mkQ` f)
where
#if ANNOTATED
f :: (Name SrcSpanInfo) -> [String] -- seems nec.
#else
-- f :: Name -> [String] -- unnec.
#endif
#if ANNOTATED
f (Ident _ x) = [x]
#else
f (Ident x) = [x]
#endif
f _ = []
-- The names which FreeSect inserts will never conflict with each other.
-- We only need to assure they don't conflict with any existing names.
-- Actually, we need to make sure the name created here is not a prefix
-- of any existing name, because we add _XY to freesect slot names.
#if ANNOTATED
fs_fresh_name :: Module SrcSpanInfo -> String
#else
fs_fresh_name :: Module -> String
#endif
fs_fresh_name m = f g
where
ss = fs_all_identifiers m
g = mkStdGen 123 -- arbitrary seed
-- The following was much simpler when accept whole of r
-- as the random part of the name -- however, that made for
-- ugly long names, and so we try for the shortest possible
-- first. (If you never inspect the intermediate code, you
-- wouldn't care if the var names were ugly...)
f :: StdGen -> String
f g | b = s
| otherwise = f g' -- unlikely
where (r,g') = next g
(b,s) = f' rs (0,length rs)
rs = show r
f' :: String -> (Int,Int) -> (Bool,String)
f' s (n,ntop) | n > ntop = (False,"")
| not fail = (True,s'')
| otherwise = f' s (1+n,ntop)
where s' = take n s
s'' = s' ++ "_"
-- s'' = "fs" ++ s' ++ "_"
fail = or $ map (isPrefixOf s'') ss
--------------------------------------------------------------------------------
#if ANNOTATED
stripFSPragma :: Module SrcSpanInfo -> Module SrcSpanInfo
#else
stripFSPragma :: Module -> Module
#endif
#if ANNOTATED
stripFSPragma (Module x1 x2 prags x4 x5)
= Module x1 x2 prags' x4 x5
#else
stripFSPragma (Module x1 x2 prags x4 x5 x6 x7)
= Module x1 x2 prags' x4 x5 x6 x7
#endif
where prags' = map f prags
f (LanguagePragma sl_or_ssi ns)
= LanguagePragma sl_or_ssi $ filter p ns
f x = x
#if ANNOTATED
p n@(Ident sl_or_ssi "FreeSections") = False
#else
p n@(Ident "FreeSections") = False
#endif
p _ = True
#if ANNOTATED
stripEmptyPragmaList :: Module SrcSpanInfo -> Module SrcSpanInfo
#else
stripEmptyPragmaList :: Module -> Module
#endif
#if ANNOTATED
stripEmptyPragmaList (Module x1 x2 prags x4 x5)
= Module x1 x2 prags' x4 x5
#else
stripEmptyPragmaList (Module x1 x2 prags x4 x5 x6 x7)
= Module x1 x2 prags' x4 x5 x6 x7
#endif
where prags' = filter p prags
p (LanguagePragma ssi []) = False
p _ = True
#if ANNOTATED
fixModuleName :: String -> Module SrcSpanInfo -> Module SrcSpanInfo
#else
fixModuleName :: String -> Module -> Module
#endif
#if ANNOTATED
fixModuleName name (Module x1 x2 x3 x4 x5)
= Module x1 name' x3 x4 x5
#else
fixModuleName name (Module x1 x2 x3 x4 x5 x6 x7)
= Module x1 name' x3 x4 x5 x6 x7
#endif
where
#if ANNOTATED
(Just (ModuleHead ssi _ mwt mesl)) = x2
name' = Just (ModuleHead ssi (ModuleName ssi name) mwt mesl)
#else
(ModuleName _) = x2
name' = ModuleName name
#endif
p (LanguagePragma ssi []) = False
p _ = True
--------------------------------------------------------------------------------
main:: IO ()
main
= do (
outfile :
lexsrc_pathnames
) <- getArgs
lexsrc_serials_ <- mapM readSourcesFromFileOrDir lexsrc_pathnames
let
(pnames,lexsrc_serials) = unzip $ concat $ reverse lexsrc_serials_
#if ANNOTATED
#if PARALLEL
parsedsrc_maybes = (runEval $ parTraversable rpar $ doParsing pnames lexsrc_serials) :: [ParseResult (Module SrcSpanInfo)]
#else
parsedsrc_maybes = (doParsing pnames lexsrc_serials) :: [ParseResult (Module SrcSpanInfo)]
#endif
#else
parsedsrc_maybes = (doParsing pnames lexsrc_serials) :: [ParseResult Module]
#endif
let
-- parsed_srcs = error $ ( ( concatMap prettyPrint $ ( ( testParses parsedsrc_maybes ) :: [Module] ) ) :: String )
#if ANNOTATED
parsed_srcs = ( testParses parsedsrc_maybes ) :: [Module SrcSpanInfo]
#else
parsed_srcs = ( testParses parsedsrc_maybes ) :: [Module]
#endif
{--
let
test = ( error $ show $ map fs_FSS_lineal_chains parsed_srcs ) :: String
print test
--}
let
#if ANNOTATED
#if PARALLEL
transformed_srcs = ( runEval $ parTraversable rpar $
map fs_module parsed_srcs
) :: [Module SrcSpanInfo]
#else
transformed_srcs = ( map fs_module parsed_srcs
) :: [Module SrcSpanInfo]
#endif
#else
#if PARALLEL
transformed_srcs = ( runEval $ parTraversable rpar $
map fs_module parsed_srcs
) :: [Module]
#else
transformed_srcs = ( map fs_module parsed_srcs
) :: [Module]
#endif
#endif
-- transformed_srcs = parsed_srcs
let
transformed_srcs' = map stripFSPragma transformed_srcs
transformed_srcs'' = map stripEmptyPragmaList transformed_srcs'
#if GHC_F
#else
transformed_srcs''' = map (fixModuleName outfile) transformed_srcs''
#endif
#if GHC_F
debug parsed_srcs transformed_srcs''
writeFile outfile $ -- with ghc -F
concatMap prettyPrint transformed_srcs''
#else
debug parsed_srcs transformed_srcs'''
writeFile (outfile++".hs") $
concatMap prettyPrint transformed_srcs'''
#endif
hFlush stdout
#if ANNOTATED
debug :: [Module SrcSpanInfo] -> [Module SrcSpanInfo] -> IO ()
#else
debug :: [Module] -> [Module] -> IO ()
#endif
debug ms ms'
= do
#if 0
putStrLn $ show ms
putStrLn $ show ms'
#endif
#if 1
putStrLn $ concatMap prettyPrint ms
putStrLn $ concatMap prettyPrint ms'
#endif
return ()