-- Package: freesect-0.8 -- Description: Extend Haskell to support free sections -- Example: zipWith (f __ b __ d) as cs -- Author: Andrew Seniuk -- Date: March 11, 2012 -- License: BSD3 (./LICENSE) -- Executable: freesect -- Usage: See accompanying files 000-readme and z {-# LANGUAGE CPP #-} -- just a couple #if 0/1's {- # LANGUAGE DeriveDataTypeable #-} -- not needed! {- # LANGUAGE FlexibleContexts #-} -- only needed in FreeSectAnnotated.hs {- # LANGUAGE MultiParamTypeClasses #-} {- # LANGUAGE RankNTypes #-} -- needed for the path accumulators {- # LANGUAGE ExistentialQuantification #-} {- # LANGUAGE GADTs #-} {- # LANGUAGE ScopedTypeVariables #-} -- needed for a pattern type sig {- # NOINLINE fs_warn_flaw #-} -- CPP definitions are set using compiler options; see ./z and ./ile. module FreeSect(fs_module) where import Data.Data(Data,gmapQi) import Data.Generics.Aliases(mkQ,mkT,mkM) import Data.Generics.Schemes(everywhere,everywhereM,gcount) --import Data.Generics -- this suffices to import all the above import Control.Monad.State import System.IO.Unsafe(unsafePerformIO) -- warning message to stderr import System.IO(hFlush,stderr,hPutStr) --import Data.IORef(IORef,newIORef) import HSE import Util -------------------------------------------------------------------------------- -- No type signatures are needed anywhere in this file (confirmed), -- but in FreeSectAnnotated some /are/ needed. -- Why does GHC complain of pattern matches overlapping in some of -- the generic transformers, but not all? The code structure is -- completely analogous so far as I can see. -- :: Module -> Module always, at present --fs_module :: Data a => a -> a -- typesig not nec. fs_module m0 = m5 where -- It's a bit annoying, but GuardedRhs and UnGuardedRhs are -- not constructors of the same data type, so we cannot use -- a single generic traversal to handle both. Could the -- duplication be avoided? m1 = fs_warn_flaw m0 -- check/warn re. 's flaw m2 = fs_unguarded_rhss m1 -- translate UnGuardedRhs's m3 = fs_guarded_rhss m2 -- translate GuardedRhs's m4 = fs_error_if_any_remain m3 -- error if any freesects remain m5 = fs_cleanup m4 -- remove some redundant Paren's --fs_warn_flaw :: Data a => a -> a -- unnec. fs_warn_flaw m = m' where m' = everywhere (mkT step) m -- step :: Exp -> Exp -- unnec. step x@(App p@(Paren (App FSWildcard _)) _) = warning True p x step x@(App p@(Paren (App _ FSWildcard)) _) = warning False p x step x = x -- warning :: Data a => Bool -> a -> a -> a -- unnec. warning b p x = unsafePerformIO $ do hPutStr stderr $ warning_message b p x hFlush stderr return x warning_message b p x = "Warning:\n" ++ " Inferring free section context of loose wildcard(s) occurring\n" ++ " in redundantly-parenthesised application\n" ++ " " ++ prettyPrint p ++ "\n" ++ " in the expression\n" ++ " " ++ prettyPrint x ++ "\n" ++ ( if b then " This means for e.g. that (f __) y is rewritten to (\\x->f x) y.\n" else " This means for e.g. that (__ x) y is rewritten to (\\f->f x) y.\n" ) -- parentheses are really key here... ++ " If this is not what you want, remove the redundant parentheses\n" ++ " or use explicit _[...]_ free section context syntax.\n" ++ " (Compile freesect with ANNOTATED=1 to get location info.)\n" -- :: Module -> Module always, at present --fs_unguarded_rhss :: Data a => a -> a -- typesig not nec. fs_unguarded_rhss m = m'' where m' = everywhere (mkT step1) m -- explicitly _[...]_ grouped freesects m'' = everywhere (mkT step2) m' -- remaining __'s get inferred context -- step1 :: Rhs -> Rhs -- unnec. step1 (UnGuardedRhs e) = UnGuardedRhs e' where e' = fs_rhs_exp fresh e step1 x = x -- step2 :: Rhs -> Rhs -- unnec. step2 x@(UnGuardedRhs e) | still_fsss = UnGuardedRhs e'' | otherwise = x where still_fsss = 0 < gcount (False `mkQ` p) x -- p :: Exp -> Bool p FSWildcard = True p _ = False e'' = fs_rhs_exp fresh e' e' = Paren e step2 x = x fresh = fs_fresh_name m -- Unfortunate about the cloning here (see comment heading fs_module above). -- :: Module -> Module always, at present --fs_guarded_rhss :: Data a => a -> a -- typesig not nec. fs_guarded_rhss m = m'' where m' = everywhere (mkT step1) m -- explicitly _[...]_ grouped freesects m'' = everywhere (mkT step2) m' -- remaining __'s get inferred context -- step1 :: GuardedRhs -> GuardedRhs -- unnec. step1 (GuardedRhs srcLoc slst e) = GuardedRhs srcLoc slst e' where e' = fs_rhs_exp fresh e step1 x = x -- step2 :: GuardedRhs -> GuardedRhs -- unnec. step2 x@(GuardedRhs srcLoc slst e) | still_fsss = GuardedRhs srcLoc slst e'' | otherwise = x where still_fsss = 0 < gcount (False `mkQ` p) x -- p :: Exp -> Bool p FSWildcard = True p _ = False e'' = fs_rhs_exp fresh e' e' = Paren e step2 x = x fresh = fs_fresh_name m -- :: Module -> Module always, at present --fs_error_if_any_remain :: Data a => a -> a -- typesig not nec. 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 -- p :: Exp -> Bool p FSWildcard = True -- p (FSContext _) = True -- dealt with subsequently in fs_cleanup p _ = False -- :: Module -> Module always, at present --fs_cleanup :: Data a => a -> a -- typesig not nec. fs_cleanup m0 = m3 where m1 = everywhere (mkT step1) m0 -- for the Rhs's (un-guarded) m2 = everywhere (mkT step2) m1 -- for the GuardedRhs's m3 = everywhere (mkT step3) m2 -- for remaining FSContext -> Paren -- step1 :: Rhs -> Rhs -- unnec. step1 (UnGuardedRhs (FSContext e)) = UnGuardedRhs e #if CLEAN_EXTRANEOUS_GROUPINGS step1 (UnGuardedRhs (InfixApp (FSContext e1) (QVarOp (UnQual (Symbol "$"))) e2)) = UnGuardedRhs (App (Paren e1) e2) #endif step1 x = x -- step2 :: GuardedRhs -> GuardedRhs -- unnec. step2 x@(GuardedRhs srcLoc slst (FSContext e)) = GuardedRhs srcLoc slst e #if CLEAN_EXTRANEOUS_GROUPINGS step2 (GuardedRhs srcLoc slst (InfixApp (FSContext e1) (QVarOp (UnQual (Symbol "$"))) e2)) = GuardedRhs srcLoc slst (App (Paren e1) e2) #endif step2 x = x -- step3 :: Exp -> Exp -- once was nec. but not now? step3 (FSContext e) = Paren e step3 x = x -------------------------------------------------------------------------------- -- Actually perform freesect translations in the immediate subexpression -- of a given RHS in the AST. Since the caller is itself a bottom-up -- generic traversal, nested freesects will get rewritten before -- enclosing freesects are processed. -- :: String -> Exp -> Exp --fs_rhs_exp :: Data a => String -> a -> a -- typesig not nec. fs_rhs_exp fresh rhs_top_exp = rhs_top_exp'' where rhs_top_exp' = everywhere (mkT step) rhs_top_exp rhs_top_exp'' | num_fss_remaining > 0 = everywhere (mkT step2) rhs_top_exp' | otherwise = rhs_top_exp' -- FSContext is the grouping node in the AST produced by freesect _[ ]_ syntax. -- The default context inferencing cases follow this explicit FSContext case. -- The Exp -> Exp type sig for step (though it works) is not needed here... -- step :: Data a => a -> a -- ...although this one won't work. step x@(FSContext e) = fs_lambda_old ps' x' where (x',(ps,_)) = fs_name_slots fresh x ps' = reverse ps #if 0 -- Just a test of generic power of SYB. A single traversal is generic, but -- only permits transformation of nodes of a single specific type. The above -- case is Exp -> Exp, while this is Decl -> Decl. step x@(DefaultDecl srcLoc ts) = fs_lambda [] x -- quick test #endif step x = x num_fss_remaining = gcount (False `mkQ` p) rhs_top_exp' -- p :: Exp -> Bool p FSWildcard = True 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. -- (Later: Added list braces (list enumerations and comprehensions) to -- the set of delimiters. This was motivated by consideration of -- primitives.html, but may need reconsideration when see more -- real-world examples.) -- -- Would prefer to use SYB "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. -- step2 :: Exp -> Exp step2 x@(Paren e) | num_fss_remaining == gcount (False `mkQ` p) e = x'_ | otherwise = x where -- (We safely discarded the Paren from the AST since FSContext will -- give Paren grouping behaviour in addition to freesect contexting.) x_ = FSContext e -- x_ = x (x',(ps,_)) = fs_name_slots fresh x_ ps' = reverse ps x'_ = fs_lambda_old ps' x' 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 (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_old ps1' e1' e2'_ = fs_lambda_old ps2' e2' x'_ = fs_lambda_old ps' x' num_fss_e1 = gcount (False `mkQ` p) e1 num_fss_e2 = gcount (False `mkQ` p) e2 num_fss_x = num_fss_e1 + num_fss_e2 -- num_fss_x = gcount (False `mkQ` p) x -- These are simpler, since we are just wrapping the node in FSContext -- and calling the fs_lambda transformer. step2 x@(List _) = process x step2 x@(EnumFrom _) = process x step2 x@(EnumFromTo _ _) = process x step2 x@(EnumFromThen _ _) = process x step2 x@(EnumFromThenTo _ _ _) = process x step2 x@(ListComp e slst) = process x step2 x = x process x | num_fss_remaining == gcount (False `mkQ` p) x = x'_ | otherwise = x where x_ = FSContext x x'_ = fs_lambda_old ps' x' (x',(ps,_)) = fs_name_slots fresh x_ ps' = reverse ps -- Actually rewrite the passed Exp branch as a Lambda. The argument is, -- at least at present, always an FSContext, but any Exp branch would be -- treated analogously without changing fs_lambda. -- Note that the Lambda itself is wrapped in a Paren; this does not -- change the semantics of the AST, but is necessary in general to -- preserve the semantics when pretty-printing as lexical sourcecode. -- :: [String] -> Exp -> Exp always, at present --fs_lambda :: Data a => [String] -> a -> a -- must NOT give this one! --fs_lambda :: [String] -> Exp -> Exp -- unnec. fs_lambda ps_lambda e_lambda -- XXX See fs_lambda_old for what we should do here now... | null ps_lambda = error $ "Error: Free section contains no wildcards.\n" ++ "(Source location unavailable; try compiling freesect " ++ "with ANNOTATED set to 1.)\n" | otherwise = lambda where lambda = Paren $ Lambda srcloc ps_lambda' e_lambda'' ps_lambda' = map (\x->(PVar (Ident x))) ps_lambda e_lambda'' = e_lambda -- e_lambda'@(FSContext e) = e_lambda -- e_lambda'' = e srcloc = SrcLoc "" 0 0 -- :: [String] -> Exp -> Exp always, at present --fs_lambda_old :: Data a => [String] -> a -> a -- must NOT give this one! --fs_lambda_old :: [String] -> Exp -> Exp -- unnec. fs_lambda_old ps_lambda e_lambda -- Now, rather than report the error, we silently convert them -- to Paren's. No harm is done with this interpretation (it -- is natural), and it allows us to keep the FSContext nodes -- around until a post-translation cleanup where they are made use of. | null ps_lambda #if 1 = FSContext e_lambda #else = error $ "Error: Free section contains no wildcards.\n" ++ "(Source location unavailable; try compiling freesect " ++ "with ANNOTATED set to 1.)\n" #endif | otherwise = lambda where -- The idea with leaving the FSContext's is, we can use -- them as markers to indicate where the rewrites happened -- (i.e. which Lambda's are due to freesect rewrites) -- and, in fs_clean, can use this to make the rewritten -- code a little bit cleaner (removing superfluous groupings -- or $ opertators). lambda = FSContext $ Lambda srcloc ps_lambda' e_lambda'' -- lambda = Paren $ 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 showSLorSSI (SrcLoc n l c) = n ++ ": line=" ++ show l ++ " col=" ++ show c -------------------------------------------------------------------------------- -- 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. -- 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! --fs_name_slots :: Data a => String -> a -> (a,([String],Int)) -- not needed fs_name_slots fresh = flip runState ([],0) . everywhereM (mkM step) where -- step :: MonadState ([String],Int) m => Exp -> m Exp -- unnec. step FSWildcard = do (ss,n) <- get let s = fresh ++ show n put ((s:ss),(1+n)) return $ Var $ UnQual $ Ident $ s step x = return x